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/* WIP, experimental: use recvmmsg() on linux 26 * we have no configure check, yet 27 * and also it is only available for _GNU_SOURCE, which 28 * we do not use otherwise. 29#define HAVE_SENDMMSG 30 */ 31#if defined(HAVE_SENDMMSG) 32#define _GNU_SOURCE 33#include <sys/socket.h> 34#undef _GNU_SOURCE 35#endif 36 37#include "curl_setup.h" 38 39#ifdef HAVE_FCNTL_H 40#include <fcntl.h> 41#endif 42#include "urldata.h" 43#include "bufq.h" 44#include "dynbuf.h" 45#include "cfilters.h" 46#include "curl_trc.h" 47#include "curl_msh3.h" 48#include "curl_ngtcp2.h" 49#include "curl_osslq.h" 50#include "curl_quiche.h" 51#include "rand.h" 52#include "vquic.h" 53#include "vquic_int.h" 54#include "strerror.h" 55 56/* The last 3 #include files should be in this order */ 57#include "curl_printf.h" 58#include "curl_memory.h" 59#include "memdebug.h" 60 61 62#ifdef ENABLE_QUIC 63 64#ifdef O_BINARY 65#define QLOGMODE O_WRONLY|O_CREAT|O_BINARY 66#else 67#define QLOGMODE O_WRONLY|O_CREAT 68#endif 69 70#define NW_CHUNK_SIZE (64 * 1024) 71#define NW_SEND_CHUNKS 2 72 73 74void Curl_quic_ver(char *p, size_t len) 75{ 76#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) 77 Curl_ngtcp2_ver(p, len); 78#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) 79 Curl_osslq_ver(p, len); 80#elif defined(USE_QUICHE) 81 Curl_quiche_ver(p, len); 82#elif defined(USE_MSH3) 83 Curl_msh3_ver(p, len); 84#endif 85} 86 87CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx) 88{ 89 Curl_bufq_init2(&qctx->sendbuf, NW_CHUNK_SIZE, NW_SEND_CHUNKS, 90 BUFQ_OPT_SOFT_LIMIT); 91#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG) 92 qctx->no_gso = FALSE; 93#else 94 qctx->no_gso = TRUE; 95#endif 96#ifdef DEBUGBUILD 97 { 98 char *p = getenv("CURL_DBG_QUIC_WBLOCK"); 99 if(p) { 100 long l = strtol(p, NULL, 10); 101 if(l >= 0 && l <= 100) 102 qctx->wblock_percent = (int)l; 103 } 104 } 105#endif 106 vquic_ctx_update_time(qctx); 107 108 return CURLE_OK; 109} 110 111void vquic_ctx_free(struct cf_quic_ctx *qctx) 112{ 113 Curl_bufq_free(&qctx->sendbuf); 114} 115 116void vquic_ctx_update_time(struct cf_quic_ctx *qctx) 117{ 118 qctx->last_op = Curl_now(); 119} 120 121static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, 122 struct Curl_easy *data, 123 struct cf_quic_ctx *qctx, 124 const uint8_t *pkt, size_t pktlen, 125 size_t gsolen, size_t *psent); 126 127static CURLcode do_sendmsg(struct Curl_cfilter *cf, 128 struct Curl_easy *data, 129 struct cf_quic_ctx *qctx, 130 const uint8_t *pkt, size_t pktlen, size_t gsolen, 131 size_t *psent) 132{ 133#ifdef HAVE_SENDMSG 134 struct iovec msg_iov; 135 struct msghdr msg = {0}; 136 ssize_t sent; 137#if defined(__linux__) && defined(UDP_SEGMENT) 138 uint8_t msg_ctrl[32]; 139 struct cmsghdr *cm; 140#endif 141 142 *psent = 0; 143 msg_iov.iov_base = (uint8_t *)pkt; 144 msg_iov.iov_len = pktlen; 145 msg.msg_iov = &msg_iov; 146 msg.msg_iovlen = 1; 147 148#if defined(__linux__) && defined(UDP_SEGMENT) 149 if(pktlen > gsolen) { 150 /* Only set this, when we need it. macOS, for example, 151 * does not seem to like a msg_control of length 0. */ 152 msg.msg_control = msg_ctrl; 153 assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t))); 154 msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); 155 cm = CMSG_FIRSTHDR(&msg); 156 cm->cmsg_level = SOL_UDP; 157 cm->cmsg_type = UDP_SEGMENT; 158 cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); 159 *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff; 160 } 161#endif 162 163 164 while((sent = sendmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR) 165 ; 166 167 if(sent == -1) { 168 switch(SOCKERRNO) { 169 case EAGAIN: 170#if EAGAIN != EWOULDBLOCK 171 case EWOULDBLOCK: 172#endif 173 return CURLE_AGAIN; 174 case EMSGSIZE: 175 /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */ 176 break; 177 case EIO: 178 if(pktlen > gsolen) { 179 /* GSO failure */ 180 failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent, 181 SOCKERRNO); 182 qctx->no_gso = TRUE; 183 return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); 184 } 185 FALLTHROUGH(); 186 default: 187 failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO); 188 return CURLE_SEND_ERROR; 189 } 190 } 191 else { 192 assert(pktlen == (size_t)sent); 193 } 194#else 195 ssize_t sent; 196 (void)gsolen; 197 198 *psent = 0; 199 200 while((sent = send(qctx->sockfd, 201 (const char *)pkt, (SEND_TYPE_ARG3)pktlen, 0)) == -1 && 202 SOCKERRNO == EINTR) 203 ; 204 205 if(sent == -1) { 206 if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { 207 return CURLE_AGAIN; 208 } 209 else { 210 failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO); 211 if(SOCKERRNO != EMSGSIZE) { 212 return CURLE_SEND_ERROR; 213 } 214 /* UDP datagram is too large; caused by PMTUD. Just let it be 215 lost. */ 216 } 217 } 218#endif 219 (void)cf; 220 *psent = pktlen; 221 222 return CURLE_OK; 223} 224 225static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, 226 struct Curl_easy *data, 227 struct cf_quic_ctx *qctx, 228 const uint8_t *pkt, size_t pktlen, 229 size_t gsolen, size_t *psent) 230{ 231 const uint8_t *p, *end = pkt + pktlen; 232 size_t sent; 233 234 *psent = 0; 235 236 for(p = pkt; p < end; p += gsolen) { 237 size_t len = CURLMIN(gsolen, (size_t)(end - p)); 238 CURLcode curlcode = do_sendmsg(cf, data, qctx, p, len, len, &sent); 239 if(curlcode != CURLE_OK) { 240 return curlcode; 241 } 242 *psent += sent; 243 } 244 245 return CURLE_OK; 246} 247 248static CURLcode vquic_send_packets(struct Curl_cfilter *cf, 249 struct Curl_easy *data, 250 struct cf_quic_ctx *qctx, 251 const uint8_t *pkt, size_t pktlen, 252 size_t gsolen, size_t *psent) 253{ 254 CURLcode result; 255#ifdef DEBUGBUILD 256 /* simulate network blocking/partial writes */ 257 if(qctx->wblock_percent > 0) { 258 unsigned char c; 259 Curl_rand(data, &c, 1); 260 if(c >= ((100-qctx->wblock_percent)*256/100)) { 261 CURL_TRC_CF(data, cf, "vquic_flush() simulate EWOULDBLOCK"); 262 return CURLE_AGAIN; 263 } 264 } 265#endif 266 if(qctx->no_gso && pktlen > gsolen) { 267 result = send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); 268 } 269 else { 270 result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent); 271 } 272 if(!result) 273 qctx->last_io = qctx->last_op; 274 return result; 275} 276 277CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data, 278 struct cf_quic_ctx *qctx) 279{ 280 const unsigned char *buf; 281 size_t blen, sent; 282 CURLcode result; 283 size_t gsolen; 284 285 while(Curl_bufq_peek(&qctx->sendbuf, &buf, &blen)) { 286 gsolen = qctx->gsolen; 287 if(qctx->split_len) { 288 gsolen = qctx->split_gsolen; 289 if(blen > qctx->split_len) 290 blen = qctx->split_len; 291 } 292 293 result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent); 294 CURL_TRC_CF(data, cf, "vquic_send(len=%zu, gso=%zu) -> %d, sent=%zu", 295 blen, gsolen, result, sent); 296 if(result) { 297 if(result == CURLE_AGAIN) { 298 Curl_bufq_skip(&qctx->sendbuf, sent); 299 if(qctx->split_len) 300 qctx->split_len -= sent; 301 } 302 return result; 303 } 304 Curl_bufq_skip(&qctx->sendbuf, sent); 305 if(qctx->split_len) 306 qctx->split_len -= sent; 307 } 308 return CURLE_OK; 309} 310 311CURLcode vquic_send(struct Curl_cfilter *cf, struct Curl_easy *data, 312 struct cf_quic_ctx *qctx, size_t gsolen) 313{ 314 qctx->gsolen = gsolen; 315 return vquic_flush(cf, data, qctx); 316} 317 318CURLcode vquic_send_tail_split(struct Curl_cfilter *cf, struct Curl_easy *data, 319 struct cf_quic_ctx *qctx, size_t gsolen, 320 size_t tail_len, size_t tail_gsolen) 321{ 322 DEBUGASSERT(Curl_bufq_len(&qctx->sendbuf) > tail_len); 323 qctx->split_len = Curl_bufq_len(&qctx->sendbuf) - tail_len; 324 qctx->split_gsolen = gsolen; 325 qctx->gsolen = tail_gsolen; 326 CURL_TRC_CF(data, cf, "vquic_send_tail_split: [%zu gso=%zu][%zu gso=%zu]", 327 qctx->split_len, qctx->split_gsolen, 328 tail_len, qctx->gsolen); 329 return vquic_flush(cf, data, qctx); 330} 331 332#ifdef HAVE_SENDMMSG 333static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, 334 struct Curl_easy *data, 335 struct cf_quic_ctx *qctx, 336 size_t max_pkts, 337 vquic_recv_pkt_cb *recv_cb, void *userp) 338{ 339#define MMSG_NUM 64 340 struct iovec msg_iov[MMSG_NUM]; 341 struct mmsghdr mmsg[MMSG_NUM]; 342 uint8_t bufs[MMSG_NUM][2*1024]; 343 struct sockaddr_storage remote_addr[MMSG_NUM]; 344 size_t total_nread, pkts; 345 int mcount, i, n; 346 char errstr[STRERROR_LEN]; 347 CURLcode result = CURLE_OK; 348 349 DEBUGASSERT(max_pkts > 0); 350 pkts = 0; 351 total_nread = 0; 352 while(pkts < max_pkts) { 353 n = (int)CURLMIN(MMSG_NUM, max_pkts); 354 memset(&mmsg, 0, sizeof(mmsg)); 355 for(i = 0; i < n; ++i) { 356 msg_iov[i].iov_base = bufs[i]; 357 msg_iov[i].iov_len = (int)sizeof(bufs[i]); 358 mmsg[i].msg_hdr.msg_iov = &msg_iov[i]; 359 mmsg[i].msg_hdr.msg_iovlen = 1; 360 mmsg[i].msg_hdr.msg_name = &remote_addr[i]; 361 mmsg[i].msg_hdr.msg_namelen = sizeof(remote_addr[i]); 362 } 363 364 while((mcount = recvmmsg(qctx->sockfd, mmsg, n, 0, NULL)) == -1 && 365 SOCKERRNO == EINTR) 366 ; 367 if(mcount == -1) { 368 if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { 369 CURL_TRC_CF(data, cf, "ingress, recvmmsg -> EAGAIN"); 370 goto out; 371 } 372 if(!cf->connected && SOCKERRNO == ECONNREFUSED) { 373 const char *r_ip = NULL; 374 int r_port = 0; 375 Curl_cf_socket_peek(cf->next, data, NULL, NULL, 376 &r_ip, &r_port, NULL, NULL); 377 failf(data, "QUIC: connection to %s port %u refused", 378 r_ip, r_port); 379 result = CURLE_COULDNT_CONNECT; 380 goto out; 381 } 382 Curl_strerror(SOCKERRNO, errstr, sizeof(errstr)); 383 failf(data, "QUIC: recvmsg() unexpectedly returned %d (errno=%d; %s)", 384 mcount, SOCKERRNO, errstr); 385 result = CURLE_RECV_ERROR; 386 goto out; 387 } 388 389 CURL_TRC_CF(data, cf, "recvmmsg() -> %d packets", mcount); 390 pkts += mcount; 391 for(i = 0; i < mcount; ++i) { 392 total_nread += mmsg[i].msg_len; 393 result = recv_cb(bufs[i], mmsg[i].msg_len, 394 mmsg[i].msg_hdr.msg_name, mmsg[i].msg_hdr.msg_namelen, 395 0, userp); 396 if(result) 397 goto out; 398 } 399 } 400 401out: 402 if(total_nread || result) 403 CURL_TRC_CF(data, cf, "recvd %zu packets with %zu bytes -> %d", 404 pkts, total_nread, result); 405 return result; 406} 407 408#elif defined(HAVE_SENDMSG) 409static CURLcode recvmsg_packets(struct Curl_cfilter *cf, 410 struct Curl_easy *data, 411 struct cf_quic_ctx *qctx, 412 size_t max_pkts, 413 vquic_recv_pkt_cb *recv_cb, void *userp) 414{ 415 struct iovec msg_iov; 416 struct msghdr msg; 417 uint8_t buf[64*1024]; 418 struct sockaddr_storage remote_addr; 419 size_t total_nread, pkts; 420 ssize_t nread; 421 char errstr[STRERROR_LEN]; 422 CURLcode result = CURLE_OK; 423 424 msg_iov.iov_base = buf; 425 msg_iov.iov_len = (int)sizeof(buf); 426 427 memset(&msg, 0, sizeof(msg)); 428 msg.msg_iov = &msg_iov; 429 msg.msg_iovlen = 1; 430 431 DEBUGASSERT(max_pkts > 0); 432 for(pkts = 0, total_nread = 0; pkts < max_pkts;) { 433 msg.msg_name = &remote_addr; 434 msg.msg_namelen = sizeof(remote_addr); 435 while((nread = recvmsg(qctx->sockfd, &msg, 0)) == -1 && 436 SOCKERRNO == EINTR) 437 ; 438 if(nread == -1) { 439 if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { 440 goto out; 441 } 442 if(!cf->connected && SOCKERRNO == ECONNREFUSED) { 443 const char *r_ip = NULL; 444 int r_port = 0; 445 Curl_cf_socket_peek(cf->next, data, NULL, NULL, 446 &r_ip, &r_port, NULL, NULL); 447 failf(data, "QUIC: connection to %s port %u refused", 448 r_ip, r_port); 449 result = CURLE_COULDNT_CONNECT; 450 goto out; 451 } 452 Curl_strerror(SOCKERRNO, errstr, sizeof(errstr)); 453 failf(data, "QUIC: recvmsg() unexpectedly returned %zd (errno=%d; %s)", 454 nread, SOCKERRNO, errstr); 455 result = CURLE_RECV_ERROR; 456 goto out; 457 } 458 459 ++pkts; 460 total_nread += (size_t)nread; 461 result = recv_cb(buf, (size_t)nread, msg.msg_name, msg.msg_namelen, 462 0, userp); 463 if(result) 464 goto out; 465 } 466 467out: 468 if(total_nread || result) 469 CURL_TRC_CF(data, cf, "recvd %zu packets with %zu bytes -> %d", 470 pkts, total_nread, result); 471 return result; 472} 473 474#else /* HAVE_SENDMMSG || HAVE_SENDMSG */ 475static CURLcode recvfrom_packets(struct Curl_cfilter *cf, 476 struct Curl_easy *data, 477 struct cf_quic_ctx *qctx, 478 size_t max_pkts, 479 vquic_recv_pkt_cb *recv_cb, void *userp) 480{ 481 uint8_t buf[64*1024]; 482 int bufsize = (int)sizeof(buf); 483 struct sockaddr_storage remote_addr; 484 socklen_t remote_addrlen = sizeof(remote_addr); 485 size_t total_nread, pkts; 486 ssize_t nread; 487 char errstr[STRERROR_LEN]; 488 CURLcode result = CURLE_OK; 489 490 DEBUGASSERT(max_pkts > 0); 491 for(pkts = 0, total_nread = 0; pkts < max_pkts;) { 492 while((nread = recvfrom(qctx->sockfd, (char *)buf, bufsize, 0, 493 (struct sockaddr *)&remote_addr, 494 &remote_addrlen)) == -1 && 495 SOCKERRNO == EINTR) 496 ; 497 if(nread == -1) { 498 if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { 499 CURL_TRC_CF(data, cf, "ingress, recvfrom -> EAGAIN"); 500 goto out; 501 } 502 if(!cf->connected && SOCKERRNO == ECONNREFUSED) { 503 const char *r_ip = NULL; 504 int r_port = 0; 505 Curl_cf_socket_peek(cf->next, data, NULL, NULL, 506 &r_ip, &r_port, NULL, NULL); 507 failf(data, "QUIC: connection to %s port %u refused", 508 r_ip, r_port); 509 result = CURLE_COULDNT_CONNECT; 510 goto out; 511 } 512 Curl_strerror(SOCKERRNO, errstr, sizeof(errstr)); 513 failf(data, "QUIC: recvfrom() unexpectedly returned %zd (errno=%d; %s)", 514 nread, SOCKERRNO, errstr); 515 result = CURLE_RECV_ERROR; 516 goto out; 517 } 518 519 ++pkts; 520 total_nread += (size_t)nread; 521 result = recv_cb(buf, (size_t)nread, &remote_addr, remote_addrlen, 522 0, userp); 523 if(result) 524 goto out; 525 } 526 527out: 528 if(total_nread || result) 529 CURL_TRC_CF(data, cf, "recvd %zu packets with %zu bytes -> %d", 530 pkts, total_nread, result); 531 return result; 532} 533#endif /* !HAVE_SENDMMSG && !HAVE_SENDMSG */ 534 535CURLcode vquic_recv_packets(struct Curl_cfilter *cf, 536 struct Curl_easy *data, 537 struct cf_quic_ctx *qctx, 538 size_t max_pkts, 539 vquic_recv_pkt_cb *recv_cb, void *userp) 540{ 541 CURLcode result; 542#if defined(HAVE_SENDMMSG) 543 result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); 544#elif defined(HAVE_SENDMSG) 545 result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); 546#else 547 result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp); 548#endif 549 if(!result) { 550 if(!qctx->got_first_byte) { 551 qctx->got_first_byte = TRUE; 552 qctx->first_byte_at = qctx->last_op; 553 } 554 qctx->last_io = qctx->last_op; 555 } 556 return result; 557} 558 559/* 560 * If the QLOGDIR environment variable is set, open and return a file 561 * descriptor to write the log to. 562 * 563 * This function returns error if something failed outside of failing to 564 * create the file. Open file success is deemed by seeing if the returned fd 565 * is != -1. 566 */ 567CURLcode Curl_qlogdir(struct Curl_easy *data, 568 unsigned char *scid, 569 size_t scidlen, 570 int *qlogfdp) 571{ 572 const char *qlog_dir = getenv("QLOGDIR"); 573 *qlogfdp = -1; 574 if(qlog_dir) { 575 struct dynbuf fname; 576 CURLcode result; 577 unsigned int i; 578 Curl_dyn_init(&fname, DYN_QLOG_NAME); 579 result = Curl_dyn_add(&fname, qlog_dir); 580 if(!result) 581 result = Curl_dyn_add(&fname, "/"); 582 for(i = 0; (i < scidlen) && !result; i++) { 583 char hex[3]; 584 msnprintf(hex, 3, "%02x", scid[i]); 585 result = Curl_dyn_add(&fname, hex); 586 } 587 if(!result) 588 result = Curl_dyn_add(&fname, ".sqlog"); 589 590 if(!result) { 591 int qlogfd = open(Curl_dyn_ptr(&fname), QLOGMODE, 592 data->set.new_file_perms); 593 if(qlogfd != -1) 594 *qlogfdp = qlogfd; 595 } 596 Curl_dyn_free(&fname); 597 if(result) 598 return result; 599 } 600 601 return CURLE_OK; 602} 603 604CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, 605 struct Curl_easy *data, 606 struct connectdata *conn, 607 const struct Curl_addrinfo *ai, 608 int transport) 609{ 610 (void)transport; 611 DEBUGASSERT(transport == TRNSPRT_QUIC); 612#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) 613 return Curl_cf_ngtcp2_create(pcf, data, conn, ai); 614#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) 615 return Curl_cf_osslq_create(pcf, data, conn, ai); 616#elif defined(USE_QUICHE) 617 return Curl_cf_quiche_create(pcf, data, conn, ai); 618#elif defined(USE_MSH3) 619 return Curl_cf_msh3_create(pcf, data, conn, ai); 620#else 621 *pcf = NULL; 622 (void)data; 623 (void)conn; 624 (void)ai; 625 return CURLE_NOT_BUILT_IN; 626#endif 627} 628 629bool Curl_conn_is_http3(const struct Curl_easy *data, 630 const struct connectdata *conn, 631 int sockindex) 632{ 633#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) 634 return Curl_conn_is_ngtcp2(data, conn, sockindex); 635#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) 636 return Curl_conn_is_osslq(data, conn, sockindex); 637#elif defined(USE_QUICHE) 638 return Curl_conn_is_quiche(data, conn, sockindex); 639#elif defined(USE_MSH3) 640 return Curl_conn_is_msh3(data, conn, sockindex); 641#else 642 return ((conn->handler->protocol & PROTO_FAMILY_HTTP) && 643 (conn->httpversion == 30)); 644#endif 645} 646 647CURLcode Curl_conn_may_http3(struct Curl_easy *data, 648 const struct connectdata *conn) 649{ 650 if(conn->transport == TRNSPRT_UNIX) { 651 /* cannot do QUIC over a unix domain socket */ 652 return CURLE_QUIC_CONNECT_ERROR; 653 } 654 if(!(conn->handler->flags & PROTOPT_SSL)) { 655 failf(data, "HTTP/3 requested for non-HTTPS URL"); 656 return CURLE_URL_MALFORMAT; 657 } 658#ifndef CURL_DISABLE_PROXY 659 if(conn->bits.socksproxy) { 660 failf(data, "HTTP/3 is not supported over a SOCKS proxy"); 661 return CURLE_URL_MALFORMAT; 662 } 663 if(conn->bits.httpproxy && conn->bits.tunnel_proxy) { 664 failf(data, "HTTP/3 is not supported over a HTTP proxy"); 665 return CURLE_URL_MALFORMAT; 666 } 667#endif 668 669 return CURLE_OK; 670} 671 672#else /* ENABLE_QUIC */ 673 674CURLcode Curl_conn_may_http3(struct Curl_easy *data, 675 const struct connectdata *conn) 676{ 677 (void)conn; 678 (void)data; 679 DEBUGF(infof(data, "QUIC is not supported in this build")); 680 return CURLE_NOT_BUILT_IN; 681} 682 683#endif /* !ENABLE_QUIC */ 684