diff options
Diffstat (limited to 'libs/libcurl/src/vquic')
| -rw-r--r-- | libs/libcurl/src/vquic/msh3.c | 44 | ||||
| -rw-r--r-- | libs/libcurl/src/vquic/ngtcp2.c | 239 | ||||
| -rw-r--r-- | libs/libcurl/src/vquic/ngtcp2.h | 7 | ||||
| -rw-r--r-- | libs/libcurl/src/vquic/quiche.c | 17 | ||||
| -rw-r--r-- | libs/libcurl/src/vquic/quiche.h | 2 |
5 files changed, 259 insertions, 50 deletions
diff --git a/libs/libcurl/src/vquic/msh3.c b/libs/libcurl/src/vquic/msh3.c index 296943b22a..c3e58e726a 100644 --- a/libs/libcurl/src/vquic/msh3.c +++ b/libs/libcurl/src/vquic/msh3.c @@ -27,7 +27,6 @@ #ifdef USE_MSH3 #include "urldata.h" -#include "curl_printf.h" #include "timeval.h" #include "multiif.h" #include "sendf.h" @@ -35,6 +34,11 @@ #include "h2h3.h" #include "msh3.h" +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + /* #define DEBUG_HTTP3 1 */ #ifdef DEBUG_HTTP3 #define H3BUGF(x) x @@ -110,7 +114,7 @@ CURLcode Curl_quic_connect(struct Curl_easy *data, socklen_t addrlen) { struct quicsocket *qs = &conn->hequic[sockindex]; - bool unsecure = !conn->ssl_config.verifypeer; + bool insecure = !conn->ssl_config.verifypeer; memset(qs, 0, sizeof(*qs)); (void)sockfd; @@ -128,7 +132,7 @@ CURLcode Curl_quic_connect(struct Curl_easy *data, qs->conn = MsH3ConnectionOpen(qs->api, conn->host.name, (uint16_t)conn->remote_port, - unsecure); + insecure); if(!qs->conn) { failf(data, "can't create msh3 connection"); if(qs->api) { @@ -222,13 +226,8 @@ static unsigned int msh3_conncheck(struct Curl_easy *data, return CONNRESULT_NONE; } -static void msh3_cleanup(struct quicsocket *qs, struct HTTP *stream) +static void disconnect(struct quicsocket *qs) { - if(stream && stream->recv_buf) { - free(stream->recv_buf); - stream->recv_buf = ZERO_NULL; - msh3_lock_uninitialize(&stream->recv_lock); - } if(qs->conn) { MsH3ConnectionClose(qs->conn); qs->conn = ZERO_NULL; @@ -242,18 +241,20 @@ static void msh3_cleanup(struct quicsocket *qs, struct HTTP *stream) static CURLcode msh3_disconnect(struct Curl_easy *data, struct connectdata *conn, bool dead_connection) { + (void)data; (void)dead_connection; H3BUGF(infof(data, "disconnecting (msh3)")); - msh3_cleanup(conn->quic, data->req.p.http); + disconnect(conn->quic); return CURLE_OK; } void Curl_quic_disconnect(struct Curl_easy *data, struct connectdata *conn, int tempindex) { + (void)data; if(conn->transport == TRNSPRT_QUIC) { - H3BUGF(infof(data, "disconnecting (curl)")); - msh3_cleanup(&conn->hequic[tempindex], data->req.p.http); + H3BUGF(infof(data, "disconnecting QUIC index %u", tempindex)); + disconnect(&conn->hequic[tempindex]); } } @@ -288,7 +289,6 @@ static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, struct HTTP *stream = IfContext; size_t total_len; (void)Request; - H3BUGF(printf("* msh3_header_received\n")); if(stream->recv_header_complete) { H3BUGF(printf("* ignoring header after data\n")); @@ -381,9 +381,6 @@ static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext) (void)stream; } -static_assert(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo), - "Sizes must match for cast below to work"); - static ssize_t msh3_stream_send(struct Curl_easy *data, int sockindex, const void *mem, @@ -396,6 +393,9 @@ static ssize_t msh3_stream_send(struct Curl_easy *data, struct h2h3req *hreq; (void)sockindex; + /* Sizes must match for cast below to work" */ + DEBUGASSERT(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo)); + H3BUGF(infof(data, "msh3_stream_send %zu", len)); if(!stream->req) { @@ -490,9 +490,19 @@ CURLcode Curl_quic_done_sending(struct Curl_easy *data) void Curl_quic_done(struct Curl_easy *data, bool premature) { - (void)data; + struct HTTP *stream = data->req.p.http; (void)premature; H3BUGF(infof(data, "Curl_quic_done")); + if(stream) { + if(stream->recv_buf) { + Curl_safefree(stream->recv_buf); + msh3_lock_uninitialize(&stream->recv_lock); + } + if(stream->req) { + MsH3RequestClose(stream->req); + stream->req = ZERO_NULL; + } + } } bool Curl_quic_data_pending(const struct Curl_easy *data) diff --git a/libs/libcurl/src/vquic/ngtcp2.c b/libs/libcurl/src/vquic/ngtcp2.c index ca9c388345..097cca44b0 100644 --- a/libs/libcurl/src/vquic/ngtcp2.c +++ b/libs/libcurl/src/vquic/ngtcp2.c @@ -38,6 +38,9 @@ #elif defined(USE_GNUTLS) #include <ngtcp2/ngtcp2_crypto_gnutls.h> #include "vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include <ngtcp2/ngtcp2_crypto_wolfssl.h> +#include "vtls/wolfssl.h" #endif #include "urldata.h" #include "sendf.h" @@ -101,6 +104,11 @@ struct h3out { "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ "%DISABLE_TLS13_COMPAT_MODE" +#elif defined(USE_WOLFSSL) +#define QUIC_CIPHERS \ + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ + "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" +#define QUIC_GROUPS "P-256:P-384:P-521" #endif /* ngtcp2 default congestion controller does not perform pacing. Limit @@ -113,7 +121,7 @@ static CURLcode ng_process_ingress(struct Curl_easy *data, static CURLcode ng_flush_egress(struct Curl_easy *data, int sockfd, struct quicsocket *qs); static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, - size_t datalen, void *user_data, + uint64_t datalen, void *user_data, void *stream_user_data); static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) @@ -202,6 +210,14 @@ static int keylog_callback(gnutls_session_t session, const char *label, Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); return 0; } +#elif defined(USE_WOLFSSL) +#if defined(HAVE_SECRET_CALLBACK) +static void keylog_callback(const WOLFSSL *ssl, const char *line) +{ + (void)ssl; + Curl_tls_keylog_write_line(line); +} +#endif #endif static int init_ngh3_conn(struct quicsocket *qs); @@ -395,7 +411,105 @@ static int quic_init_ssl(struct quicsocket *qs) gnutls_server_name_set(qs->ssl, GNUTLS_NAME_DNS, hostname, strlen(hostname)); return 0; } +#elif defined(USE_WOLFSSL) + +static WOLFSSL_CTX *quic_ssl_ctx(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + + if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); + return NULL; + } + + wolfSSL_CTX_set_default_verify_paths(ssl_ctx); + + if(wolfSSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) { + char error_buffer[256]; + ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); + failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer); + return NULL; + } + + if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) { + failf(data, "SSL_CTX_set1_groups_list failed"); + return NULL; + } + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { +#if defined(HAVE_SECRET_CALLBACK) + wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); +#else + failf(data, "wolfSSL was built without keylog callback"); + return NULL; #endif + } + + if(conn->ssl_config.verifypeer) { + const char * const ssl_cafile = conn->ssl_config.CAfile; + const char * const ssl_capath = conn->ssl_config.CApath; + + if(ssl_cafile || ssl_capath) { + wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + /* tell wolfSSL where to find CA certificates that are used to verify + the server's certificate. */ + if(!wolfSSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate verify locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + return NULL; + } + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + } +#ifdef CURL_CA_FALLBACK + else { + /* verifying the peer without any CA certificates won't work so + use wolfssl's built-in default as fallback */ + wolfSSL_CTX_set_default_verify_paths(ssl_ctx); + } +#endif + } + else { + wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); + } + + return ssl_ctx; +} + +/** SSL callbacks ***/ + +static int quic_init_ssl(struct quicsocket *qs) +{ + const uint8_t *alpn = NULL; + size_t alpnlen = 0; + /* this will need some attention when HTTPS proxy over QUIC get fixed */ + const char * const hostname = qs->conn->host.name; + + DEBUGASSERT(!qs->ssl); + qs->ssl = SSL_new(qs->sslctx); + + wolfSSL_set_app_data(qs->ssl, &qs->conn_ref); + wolfSSL_set_connect_state(qs->ssl); + wolfSSL_set_quic_use_legacy_codepoint(qs->ssl, 0); + + alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; + alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; + if(alpn) + wolfSSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen); + + /* set SNI */ + wolfSSL_UseSNI(qs->ssl, WOLFSSL_SNI_HOST_NAME, + hostname, (unsigned short)strlen(hostname)); + + return 0; +} +#endif /* defined(USE_WOLFSSL) */ static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data) { @@ -646,6 +760,7 @@ static ngtcp2_callbacks ng_callbacks = { NULL, /* version_negotiation */ cb_recv_rx_key, NULL, /* recv_tx_key */ + NULL, /* early_data_rejected */ }; /* @@ -691,6 +806,10 @@ CURLcode Curl_quic_connect(struct Curl_easy *data, result = quic_set_client_cert(data, qs); if(result) return result; +#elif defined(USE_WOLFSSL) + qs->sslctx = quic_ssl_ctx(data); + if(!qs->sslctx) + return CURLE_QUIC_CONNECT_ERROR; #endif if(quic_init_ssl(qs)) @@ -818,6 +937,8 @@ static void qs_disconnect(struct quicsocket *qs) SSL_free(qs->ssl); #elif defined(USE_GNUTLS) gnutls_deinit(qs->ssl); +#elif defined(USE_WOLFSSL) + wolfSSL_free(qs->ssl); #endif qs->ssl = NULL; #ifdef USE_GNUTLS @@ -831,6 +952,8 @@ static void qs_disconnect(struct quicsocket *qs) ngtcp2_conn_del(qs->qconn); #ifdef USE_OPENSSL SSL_CTX_free(qs->sslctx); +#elif defined(USE_WOLFSSL) + wolfSSL_CTX_free(qs->sslctx); #endif } @@ -899,6 +1022,7 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, H3BUGF(infof(data, "cb_h3_stream_close CALLED")); stream->closed = TRUE; + stream->error3 = app_error_code; Curl_expire(data, 0, EXPIRE_QUIC); /* make sure that ngh3_stream_recv is called again to complete the transfer even if there are no more packets to be received from the server. */ @@ -1010,6 +1134,10 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, return -1; } } + + if(stream->status_code / 100 != 1) { + stream->bodystarted = TRUE; + } return 0; } @@ -1032,9 +1160,10 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, if(token == NGHTTP3_QPACK_TOKEN__STATUS) { char line[14]; /* status line is always 13 characters long */ size_t ncopy; - int status = decode_status_code(h3val.base, h3val.len); - DEBUGASSERT(status != -1); - ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", status); + stream->status_code = decode_status_code(h3val.base, h3val.len); + DEBUGASSERT(stream->status_code != -1); + ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", + stream->status_code); result = write_data(stream, line, ncopy); if(result) { return -1; @@ -1064,16 +1193,36 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, return 0; } -static int cb_h3_send_stop_sending(nghttp3_conn *conn, int64_t stream_id, - uint64_t app_error_code, - void *user_data, - void *stream_user_data) +static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + struct quicsocket *qs = user_data; + int rv; + (void)conn; + (void)stream_user_data; + + rv = ngtcp2_conn_shutdown_stream_read(qs->qconn, stream_id, app_error_code); + if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + struct quicsocket *qs = user_data; + int rv; (void)conn; - (void)stream_id; - (void)app_error_code; - (void)user_data; (void)stream_user_data; + + rv = ngtcp2_conn_shutdown_stream_write(qs->qconn, stream_id, app_error_code); + if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; } @@ -1088,9 +1237,9 @@ static nghttp3_callbacks ngh3_callbacks = { NULL, /* begin_trailers */ cb_h3_recv_header, NULL, /* end_trailers */ - cb_h3_send_stop_sending, + cb_h3_stop_sending, NULL, /* end_stream */ - NULL, /* reset_stream */ + cb_h3_reset_stream, NULL /* shutdown */ }; @@ -1226,6 +1375,24 @@ static ssize_t ngh3_stream_recv(struct Curl_easy *data, } if(stream->closed) { + if(stream->error3 != NGHTTP3_H3_NO_ERROR) { + failf(data, + "HTTP/3 stream %" PRId64 " was not closed cleanly: (err %" PRIu64 + ")", + stream->stream3_id, stream->error3); + *curlcode = CURLE_HTTP3; + return -1; + } + + if(!stream->bodystarted) { + failf(data, + "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" + " all response header fields, treated as error", + stream->stream3_id); + *curlcode = CURLE_HTTP3; + return -1; + } + *curlcode = CURLE_OK; return 0; } @@ -1237,7 +1404,7 @@ static ssize_t ngh3_stream_recv(struct Curl_easy *data, /* this amount of data has now been acked on this stream */ static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, - size_t datalen, void *user_data, + uint64_t datalen, void *user_data, void *stream_user_data) { struct Curl_easy *data = stream_user_data; @@ -1375,6 +1542,7 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem, nva[i].namelen = hreq->header[i].namelen; nva[i].value = (unsigned char *)hreq->header[i].value; nva[i].valuelen = hreq->header[i].valuelen; + nva[i].flags = NGHTTP3_NV_FLAG_NONE; } } @@ -1443,6 +1611,11 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data, curl_socket_t sockfd = conn->sock[sockindex]; struct HTTP *stream = data->req.p.http; + if(stream->closed) { + *curlcode = CURLE_HTTP3; + return -1; + } + if(!stream->h3req) { CURLcode result = http_request(data, mem, len); if(result) { @@ -1519,12 +1692,23 @@ static CURLcode ng_has_connected(struct Curl_easy *data, if(result) return result; infof(data, "Verified certificate just fine"); -#else +#elif defined(USE_GNUTLS) result = Curl_gtls_verifyserver(data, conn, conn->quic->ssl, FIRSTSOCKET); +#elif defined(USE_WOLFSSL) + char *snihost = Curl_ssl_snihost(data, SSL_HOST_NAME(), NULL); + if(!snihost || + (wolfSSL_check_domain_name(conn->quic->ssl, snihost) == SSL_FAILURE)) + return CURLE_PEER_FAILED_VERIFICATION; + infof(data, "Verified certificate just fine"); #endif } else infof(data, "Skipped certificate verification"); +#ifdef USE_OPENSSL + if(data->set.ssl.certinfo) + /* asked to gather certificate info */ + (void)Curl_ossl_certchain(data, conn->quic->ssl); +#endif return result; } @@ -1650,15 +1834,17 @@ static CURLcode do_sendmsg(size_t *psent, struct Curl_easy *data, int sockfd, size_t pktlen, size_t gsolen) { #ifdef HAVE_SENDMSG - struct iovec msg_iov = {(void *)pkt, pktlen}; + struct iovec msg_iov; struct msghdr msg = {0}; - uint8_t msg_ctrl[32]; ssize_t sent; #if defined(__linux__) && defined(UDP_SEGMENT) + uint8_t msg_ctrl[32]; struct cmsghdr *cm; #endif *psent = 0; + msg_iov.iov_base = (uint8_t *)pkt; + msg_iov.iov_len = pktlen; msg.msg_iov = &msg_iov; msg.msg_iovlen = 1; @@ -1803,9 +1989,9 @@ static CURLcode ng_flush_egress(struct Curl_easy *data, ngtcp2_ssize outlen; uint8_t *outpos = qs->pktbuf; size_t max_udp_payload_size = - ngtcp2_conn_get_max_udp_payload_size(qs->qconn); + ngtcp2_conn_get_max_tx_udp_payload_size(qs->qconn); size_t path_max_udp_payload_size = - ngtcp2_conn_get_path_max_udp_payload_size(qs->qconn); + ngtcp2_conn_get_path_max_tx_udp_payload_size(qs->qconn); size_t max_pktcnt = CURLMIN(MAX_PKT_BURST, qs->pktbuflen / max_udp_payload_size); size_t pktcnt = 0; @@ -1889,22 +2075,11 @@ static CURLcode ng_flush_egress(struct Curl_easy *data, switch(outlen) { case NGTCP2_ERR_STREAM_DATA_BLOCKED: assert(ndatalen == -1); - rv = nghttp3_conn_block_stream(qs->h3conn, stream_id); - if(rv) { - failf(data, "nghttp3_conn_block_stream returned error: %s\n", - nghttp3_strerror(rv)); - return CURLE_SEND_ERROR; - } + nghttp3_conn_block_stream(qs->h3conn, stream_id); continue; case NGTCP2_ERR_STREAM_SHUT_WR: assert(ndatalen == -1); - rv = nghttp3_conn_shutdown_stream_write(qs->h3conn, stream_id); - if(rv) { - failf(data, - "nghttp3_conn_shutdown_stream_write returned error: %s\n", - nghttp3_strerror(rv)); - return CURLE_SEND_ERROR; - } + nghttp3_conn_shutdown_stream_write(qs->h3conn, stream_id); continue; case NGTCP2_ERR_WRITE_MORE: assert(ndatalen >= 0); diff --git a/libs/libcurl/src/vquic/ngtcp2.h b/libs/libcurl/src/vquic/ngtcp2.h index 23fbcb66df..6539f5fef3 100644 --- a/libs/libcurl/src/vquic/ngtcp2.h +++ b/libs/libcurl/src/vquic/ngtcp2.h @@ -38,6 +38,10 @@ #include <openssl/ssl.h> #elif defined(USE_GNUTLS) #include <gnutls/gnutls.h> +#elif defined(USE_WOLFSSL) +#include <wolfssl/options.h> +#include <wolfssl/ssl.h> +#include <wolfssl/quic.h> #endif struct blocked_pkt { @@ -62,6 +66,9 @@ struct quicsocket { #elif defined(USE_GNUTLS) gnutls_certificate_credentials_t cred; gnutls_session_t ssl; +#elif defined(USE_WOLFSSL) + WOLFSSL_CTX *sslctx; + WOLFSSL *ssl; #endif struct sockaddr_storage local_addr; socklen_t local_addrlen; diff --git a/libs/libcurl/src/vquic/quiche.c b/libs/libcurl/src/vquic/quiche.c index 9a2b74310a..a52a7e8e21 100644 --- a/libs/libcurl/src/vquic/quiche.c +++ b/libs/libcurl/src/vquic/quiche.c @@ -258,6 +258,7 @@ CURLcode Curl_quic_connect(struct Curl_easy *data, struct quicsocket *qs = &conn->hequic[sockindex]; char ipbuf[40]; int port; + int rv; #ifdef DEBUG_QUICHE /* initialize debug log callback only once */ @@ -303,8 +304,16 @@ CURLcode Curl_quic_connect(struct Curl_easy *data, if(result) return result; + qs->local_addrlen = sizeof(qs->local_addr); + rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr, + &qs->local_addrlen); + if(rv == -1) + return CURLE_QUIC_CONNECT_ERROR; + qs->conn = quiche_conn_new_with_tls((const uint8_t *) qs->scid, - sizeof(qs->scid), NULL, 0, addr, addrlen, + sizeof(qs->scid), NULL, 0, + (struct sockaddr *)&qs->local_addr, + qs->local_addrlen, addr, addrlen, qs->cfg, qs->ssl, false); if(!qs->conn) { failf(data, "can't create quiche connection"); @@ -407,6 +416,10 @@ static CURLcode quiche_has_connected(struct Curl_easy *data, qs->cfg = NULL; qs->conn = NULL; } + if(data->set.ssl.certinfo) + /* asked to gather certificate info */ + (void)Curl_ossl_certchain(data, qs->ssl); + return CURLE_OK; fail: quiche_h3_config_free(qs->h3config); @@ -478,6 +491,8 @@ static CURLcode process_ingress(struct Curl_easy *data, int sockfd, recv_info.from = (struct sockaddr *) &from; recv_info.from_len = from_len; + recv_info.to = (struct sockaddr *) &qs->local_addr; + recv_info.to_len = qs->local_addrlen; recvd = quiche_conn_recv(qs->conn, buf, recvd, &recv_info); if(recvd == QUICHE_ERR_DONE) diff --git a/libs/libcurl/src/vquic/quiche.h b/libs/libcurl/src/vquic/quiche.h index de68089451..2da65f5f48 100644 --- a/libs/libcurl/src/vquic/quiche.h +++ b/libs/libcurl/src/vquic/quiche.h @@ -49,6 +49,8 @@ struct quicsocket { SSL_CTX *sslctx; SSL *ssl; bool h3_recving; /* TRUE when in h3-body-reading state */ + struct sockaddr_storage local_addr; + socklen_t local_addrlen; }; #endif |
