diff options
Diffstat (limited to 'libs/libcurl/src/vquic')
-rw-r--r-- | libs/libcurl/src/vquic/msh3.c | 14 | ||||
-rw-r--r-- | libs/libcurl/src/vquic/msh3.h | 2 | ||||
-rw-r--r-- | libs/libcurl/src/vquic/ngtcp2.c | 624 | ||||
-rw-r--r-- | libs/libcurl/src/vquic/ngtcp2.h | 29 | ||||
-rw-r--r-- | libs/libcurl/src/vquic/quiche.c | 51 | ||||
-rw-r--r-- | libs/libcurl/src/vquic/quiche.h | 2 | ||||
-rw-r--r-- | libs/libcurl/src/vquic/vquic.c | 2 | ||||
-rw-r--r-- | libs/libcurl/src/vquic/vquic.h | 2 |
8 files changed, 486 insertions, 240 deletions
diff --git a/libs/libcurl/src/vquic/msh3.c b/libs/libcurl/src/vquic/msh3.c index f7bd315be1..296943b22a 100644 --- a/libs/libcurl/src/vquic/msh3.c +++ b/libs/libcurl/src/vquic/msh3.c @@ -18,6 +18,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include "curl_setup.h" @@ -500,4 +502,16 @@ bool Curl_quic_data_pending(const struct Curl_easy *data) return stream->recv_header_len || stream->recv_data_len; } +/* + * Called from transfer.c:Curl_readwrite when neither HTTP level read + * nor write is performed. It is a good place to handle timer expiry + * for QUIC transport. + */ +CURLcode Curl_quic_idle(struct Curl_easy *data) +{ + (void)data; + H3BUGF(infof(data, "Curl_quic_idle")); + return CURLE_OK; +} + #endif /* USE_MSH3 */ diff --git a/libs/libcurl/src/vquic/msh3.h b/libs/libcurl/src/vquic/msh3.h index bacdcb1321..ce884d92dd 100644 --- a/libs/libcurl/src/vquic/msh3.h +++ b/libs/libcurl/src/vquic/msh3.h @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include "curl_setup.h" diff --git a/libs/libcurl/src/vquic/ngtcp2.c b/libs/libcurl/src/vquic/ngtcp2.c index f1a64eea85..ca9c388345 100644 --- a/libs/libcurl/src/vquic/ngtcp2.c +++ b/libs/libcurl/src/vquic/ngtcp2.c @@ -18,17 +18,22 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include "curl_setup.h" #ifdef USE_NGTCP2 #include <ngtcp2/ngtcp2.h> -#include <ngtcp2/ngtcp2_crypto.h> #include <nghttp3/nghttp3.h> #ifdef USE_OPENSSL #include <openssl/err.h> +#ifdef OPENSSL_IS_BORINGSSL +#include <ngtcp2/ngtcp2_crypto_boringssl.h> +#else #include <ngtcp2/ngtcp2_crypto_openssl.h> +#endif #include "vtls/openssl.h" #elif defined(USE_GNUTLS) #include <ngtcp2/ngtcp2_crypto_gnutls.h> @@ -98,6 +103,10 @@ struct h3out { "%DISABLE_TLS13_COMPAT_MODE" #endif +/* ngtcp2 default congestion controller does not perform pacing. Limit + the maximum packet burst to MAX_PKT_BURST packets. */ +#define MAX_PKT_BURST 10 + static CURLcode ng_process_ingress(struct Curl_easy *data, curl_socket_t sockfd, struct quicsocket *qs); @@ -107,6 +116,12 @@ static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, size_t datalen, void *user_data, void *stream_user_data); +static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) +{ + struct quicsocket *qs = conn_ref->user_data; + return qs->qconn; +} + static ngtcp2_tstamp timestamp(void) { struct curltime ct = Curl_now(); @@ -191,87 +206,32 @@ static int keylog_callback(gnutls_session_t session, const char *label, static int init_ngh3_conn(struct quicsocket *qs); -static int write_client_handshake(struct quicsocket *qs, - ngtcp2_crypto_level level, - const uint8_t *data, size_t len) -{ - int rv; - - rv = ngtcp2_conn_submit_crypto_data(qs->qconn, level, data, len); - if(rv) { - H3BUGF(fprintf(stderr, "write_client_handshake failed\n")); - } - assert(0 == rv); - - return 1; -} - #ifdef USE_OPENSSL -static int quic_set_encryption_secrets(SSL *ssl, - OSSL_ENCRYPTION_LEVEL ossl_level, - const uint8_t *rx_secret, - const uint8_t *tx_secret, - size_t secretlen) -{ - struct quicsocket *qs = (struct quicsocket *)SSL_get_app_data(ssl); - int level = ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level); - - if(ngtcp2_crypto_derive_and_install_rx_key( - qs->qconn, NULL, NULL, NULL, level, rx_secret, secretlen) != 0) - return 0; - - if(ngtcp2_crypto_derive_and_install_tx_key( - qs->qconn, NULL, NULL, NULL, level, tx_secret, secretlen) != 0) - return 0; - - if(level == NGTCP2_CRYPTO_LEVEL_APPLICATION) { - if(init_ngh3_conn(qs) != CURLE_OK) - return 0; - } - - return 1; -} - -static int quic_add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, - const uint8_t *data, size_t len) -{ - struct quicsocket *qs = (struct quicsocket *)SSL_get_app_data(ssl); - ngtcp2_crypto_level level = - ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level); - - return write_client_handshake(qs, level, data, len); -} - -static int quic_flush_flight(SSL *ssl) -{ - (void)ssl; - return 1; -} - -static int quic_send_alert(SSL *ssl, enum ssl_encryption_level_t level, - uint8_t alert) -{ - struct quicsocket *qs = (struct quicsocket *)SSL_get_app_data(ssl); - (void)level; - - qs->tls_alert = alert; - return 1; -} - -static SSL_QUIC_METHOD quic_method = {quic_set_encryption_secrets, - quic_add_handshake_data, - quic_flush_flight, quic_send_alert}; - static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data) { struct connectdata *conn = data->conn; SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); - SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); - SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); +#ifdef OPENSSL_IS_BORINGSSL + if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); + return NULL; + } +#else + if(ngtcp2_crypto_openssl_configure_client_context(ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_openssl_configure_client_context failed"); + return NULL; + } +#endif SSL_CTX_set_default_verify_paths(ssl_ctx); +#ifdef OPENSSL_IS_BORINGSSL + if(SSL_CTX_set1_curves_list(ssl_ctx, QUIC_GROUPS) != 1) { + failf(data, "SSL_CTX_set1_curves_list failed"); + return NULL; + } +#else if(SSL_CTX_set_ciphersuites(ssl_ctx, QUIC_CIPHERS) != 1) { char error_buffer[256]; ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); @@ -283,8 +243,7 @@ static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data) failf(data, "SSL_CTX_set1_groups_list failed"); return NULL; } - - SSL_CTX_set_quic_method(ssl_ctx, &quic_method); +#endif /* Open the file if a TLS or QUIC backend has not done this before. */ Curl_tls_keylog_open(); @@ -353,7 +312,7 @@ static int quic_init_ssl(struct quicsocket *qs) DEBUGASSERT(!qs->ssl); qs->ssl = SSL_new(qs->sslctx); - SSL_set_app_data(qs->ssl, qs); + SSL_set_app_data(qs->ssl, &qs->conn_ref); SSL_set_connect_state(qs->ssl); SSL_set_quic_use_legacy_codepoint(qs->ssl, 0); @@ -367,107 +326,6 @@ static int quic_init_ssl(struct quicsocket *qs) return 0; } #elif defined(USE_GNUTLS) -static int secret_func(gnutls_session_t ssl, - gnutls_record_encryption_level_t gtls_level, - const void *rx_secret, - const void *tx_secret, size_t secretlen) -{ - struct quicsocket *qs = gnutls_session_get_ptr(ssl); - int level = - ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(gtls_level); - - if(level != NGTCP2_CRYPTO_LEVEL_EARLY && - ngtcp2_crypto_derive_and_install_rx_key( - qs->qconn, NULL, NULL, NULL, level, rx_secret, secretlen) != 0) - return 0; - - if(ngtcp2_crypto_derive_and_install_tx_key( - qs->qconn, NULL, NULL, NULL, level, tx_secret, secretlen) != 0) - return 0; - - if(level == NGTCP2_CRYPTO_LEVEL_APPLICATION) { - if(init_ngh3_conn(qs) != CURLE_OK) - return -1; - } - - return 0; -} - -static int read_func(gnutls_session_t ssl, - gnutls_record_encryption_level_t gtls_level, - gnutls_handshake_description_t htype, const void *data, - size_t len) -{ - struct quicsocket *qs = gnutls_session_get_ptr(ssl); - ngtcp2_crypto_level level = - ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(gtls_level); - int rv; - - if(htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC) - return 0; - - rv = write_client_handshake(qs, level, data, len); - if(rv == 0) - return -1; - - return 0; -} - -static int alert_read_func(gnutls_session_t ssl, - gnutls_record_encryption_level_t gtls_level, - gnutls_alert_level_t alert_level, - gnutls_alert_description_t alert_desc) -{ - struct quicsocket *qs = gnutls_session_get_ptr(ssl); - (void)gtls_level; - (void)alert_level; - - qs->tls_alert = alert_desc; - return 1; -} - -static int tp_recv_func(gnutls_session_t ssl, const uint8_t *data, - size_t data_size) -{ - struct quicsocket *qs = gnutls_session_get_ptr(ssl); - ngtcp2_transport_params params; - - if(ngtcp2_decode_transport_params( - ¶ms, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, - data, data_size) != 0) - return -1; - - if(ngtcp2_conn_set_remote_transport_params(qs->qconn, ¶ms) != 0) - return -1; - - return 0; -} - -static int tp_send_func(gnutls_session_t ssl, gnutls_buffer_t extdata) -{ - struct quicsocket *qs = gnutls_session_get_ptr(ssl); - uint8_t paramsbuf[64]; - ngtcp2_transport_params params; - ssize_t nwrite; - int rc; - - ngtcp2_conn_get_local_transport_params(qs->qconn, ¶ms); - nwrite = ngtcp2_encode_transport_params( - paramsbuf, sizeof(paramsbuf), NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, - ¶ms); - if(nwrite < 0) { - H3BUGF(fprintf(stderr, "ngtcp2_encode_transport_params: %s\n", - ngtcp2_strerror((int)nwrite))); - return -1; - } - - rc = gnutls_buffer_append_data(extdata, paramsbuf, nwrite); - if(rc < 0) - return rc; - - return (int)nwrite; -} - static int quic_init_ssl(struct quicsocket *qs) { gnutls_datum_t alpn[2]; @@ -478,26 +336,17 @@ static int quic_init_ssl(struct quicsocket *qs) DEBUGASSERT(!qs->ssl); gnutls_init(&qs->ssl, GNUTLS_CLIENT); - gnutls_session_set_ptr(qs->ssl, qs); + gnutls_session_set_ptr(qs->ssl, &qs->conn_ref); - rc = gnutls_priority_set_direct(qs->ssl, QUIC_PRIORITY, NULL); - if(rc < 0) { - H3BUGF(fprintf(stderr, "gnutls_priority_set_direct failed: %s\n", - gnutls_strerror(rc))); + if(ngtcp2_crypto_gnutls_configure_client_session(qs->ssl) != 0) { + H3BUGF(fprintf(stderr, + "ngtcp2_crypto_gnutls_configure_client_session failed\n")); return 1; } - gnutls_handshake_set_secret_function(qs->ssl, secret_func); - gnutls_handshake_set_read_function(qs->ssl, read_func); - gnutls_alert_set_read_function(qs->ssl, alert_read_func); - - rc = gnutls_session_ext_register(qs->ssl, "QUIC Transport Parameters", - NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1, GNUTLS_EXT_TLS, - tp_recv_func, tp_send_func, NULL, NULL, NULL, - GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | - GNUTLS_EXT_FLAG_EE); + rc = gnutls_priority_set_direct(qs->ssl, QUIC_PRIORITY, NULL); if(rc < 0) { - H3BUGF(fprintf(stderr, "gnutls_session_ext_register failed: %s\n", + H3BUGF(fprintf(stderr, "gnutls_priority_set_direct failed: %s\n", gnutls_strerror(rc))); return 1; } @@ -571,7 +420,7 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, void *user_data, void *stream_user_data) { struct quicsocket *qs = (struct quicsocket *)user_data; - ssize_t nconsumed; + nghttp3_ssize nconsumed; int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; (void)offset; (void)stream_user_data; @@ -579,6 +428,9 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, nconsumed = nghttp3_conn_read_stream(qs->h3conn, stream_id, buf, buflen, fin); if(nconsumed < 0) { + ngtcp2_connection_close_error_set_application_error( + &qs->last_error, nghttp3_err_infer_quic_app_error_code((int)nconsumed), + NULL, 0); return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -629,6 +481,8 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, rv = nghttp3_conn_close_stream(qs->h3conn, stream_id, app_error_code); if(rv) { + ngtcp2_connection_close_error_set_application_error( + &qs->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -735,6 +589,23 @@ static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid, return 0; } +static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_crypto_level level, + void *user_data) +{ + struct quicsocket *qs = (struct quicsocket *)user_data; + (void)tconn; + + if(level != NGTCP2_CRYPTO_LEVEL_APPLICATION) { + return 0; + } + + if(init_ngh3_conn(qs) != CURLE_OK) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + static ngtcp2_callbacks ng_callbacks = { ngtcp2_crypto_client_initial_cb, NULL, /* recv_client_initial */ @@ -773,6 +644,8 @@ static ngtcp2_callbacks ng_callbacks = { ngtcp2_crypto_get_path_challenge_data_cb, cb_stream_stop_sending, NULL, /* version_negotiation */ + cb_recv_rx_key, + NULL, /* recv_tx_key */ }; /* @@ -855,6 +728,29 @@ CURLcode Curl_quic_connect(struct Curl_easy *data, ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->ssl); + ngtcp2_connection_close_error_default(&qs->last_error); + +#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG) + qs->no_gso = FALSE; +#else + qs->no_gso = TRUE; +#endif + + qs->num_blocked_pkt = 0; + qs->num_blocked_pkt_sent = 0; + memset(&qs->blocked_pkt, 0, sizeof(qs->blocked_pkt)); + + qs->pktbuflen = NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE * MAX_PKT_BURST; + qs->pktbuf = malloc(qs->pktbuflen); + if(!qs->pktbuf) { + ngtcp2_conn_del(qs->qconn); + qs->qconn = NULL; + return CURLE_OUT_OF_MEMORY; + } + + qs->conn_ref.get_conn = get_conn; + qs->conn_ref.user_data = qs; + return CURLE_OK; } @@ -899,18 +795,14 @@ static void qs_disconnect(struct quicsocket *qs) char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; ngtcp2_tstamp ts; ngtcp2_ssize rc; - ngtcp2_connection_close_error errorcode; if(!qs->conn) /* already closed */ return; - ngtcp2_connection_close_error_set_application_error(&errorcode, - NGHTTP3_H3_NO_ERROR, - NULL, 0); ts = timestamp(); rc = ngtcp2_conn_write_connection_close(qs->qconn, NULL, /* path */ NULL, /* pkt_info */ (uint8_t *)buffer, sizeof(buffer), - &errorcode, ts); + &qs->last_error, ts); if(rc > 0) { while((send(qs->conn->sock[FIRSTSOCKET], buffer, rc, 0) == -1) && SOCKERRNO == EINTR); @@ -934,6 +826,7 @@ static void qs_disconnect(struct quicsocket *qs) qs->cred = NULL; } #endif + free(qs->pktbuf); nghttp3_conn_del(qs->h3conn); ngtcp2_conn_del(qs->qconn); #ifdef USE_OPENSSL @@ -1368,10 +1261,10 @@ static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, return 0; } -static ssize_t cb_h3_readfunction(nghttp3_conn *conn, int64_t stream_id, - nghttp3_vec *vec, size_t veccnt, - uint32_t *pflags, void *user_data, - void *stream_user_data) +static nghttp3_ssize cb_h3_readfunction(nghttp3_conn *conn, int64_t stream_id, + nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags, void *user_data, + void *stream_user_data) { struct Curl_easy *data = stream_user_data; size_t nread; @@ -1704,7 +1597,17 @@ static CURLcode ng_process_ingress(struct Curl_easy *data, rv = ngtcp2_conn_read_pkt(qs->qconn, &path, &pi, buf, recvd, ts); if(rv) { - /* TODO Send CONNECTION_CLOSE if possible */ + if(!qs->last_error.error_code) { + if(rv == NGTCP2_ERR_CRYPTO) { + ngtcp2_connection_close_error_set_transport_error_tls_alert( + &qs->last_error, ngtcp2_conn_get_tls_alert(qs->qconn), NULL, 0); + } + else { + ngtcp2_connection_close_error_set_transport_error_liberr( + &qs->last_error, rv, NULL, 0); + } + } + if(rv == NGTCP2_ERR_CRYPTO) /* this is a "TLS problem", but a failed certificate verification is a common reason for this */ @@ -1716,32 +1619,229 @@ static CURLcode ng_process_ingress(struct Curl_easy *data, return CURLE_OK; } +static CURLcode do_sendmsg(size_t *sent, struct Curl_easy *data, int sockfd, + struct quicsocket *qs, const uint8_t *pkt, + size_t pktlen, size_t gsolen); + +static CURLcode send_packet_no_gso(size_t *psent, struct Curl_easy *data, + int sockfd, struct quicsocket *qs, + const uint8_t *pkt, size_t pktlen, + size_t gsolen) +{ + const uint8_t *p, *end = pkt + pktlen; + size_t sent; + + *psent = 0; + + for(p = pkt; p < end; p += gsolen) { + size_t len = CURLMIN(gsolen, (size_t)(end - p)); + CURLcode curlcode = do_sendmsg(&sent, data, sockfd, qs, p, len, len); + if(curlcode != CURLE_OK) { + return curlcode; + } + *psent += sent; + } + + return CURLE_OK; +} + +static CURLcode do_sendmsg(size_t *psent, struct Curl_easy *data, int sockfd, + struct quicsocket *qs, const uint8_t *pkt, + size_t pktlen, size_t gsolen) +{ +#ifdef HAVE_SENDMSG + struct iovec msg_iov = {(void *)pkt, pktlen}; + struct msghdr msg = {0}; + uint8_t msg_ctrl[32]; + ssize_t sent; +#if defined(__linux__) && defined(UDP_SEGMENT) + struct cmsghdr *cm; +#endif + + *psent = 0; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + +#if defined(__linux__) && defined(UDP_SEGMENT) + if(pktlen > gsolen) { + /* Only set this, when we need it. macOS, for example, + * does not seem to like a msg_control of length 0. */ + msg.msg_control = msg_ctrl; + assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t))); + msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); + cm = CMSG_FIRSTHDR(&msg); + cm->cmsg_level = SOL_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff; + } +#endif + + + while((sent = sendmsg(sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR) + ; + + if(sent == -1) { + switch(SOCKERRNO) { + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + return CURLE_AGAIN; + case EMSGSIZE: + /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */ + break; + case EIO: + if(pktlen > gsolen) { + /* GSO failure */ + failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent, + SOCKERRNO); + qs->no_gso = TRUE; + return send_packet_no_gso(psent, data, sockfd, qs, pkt, pktlen, + gsolen); + } + /* FALLTHROUGH */ + default: + failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO); + return CURLE_SEND_ERROR; + } + } + else { + assert(pktlen == (size_t)sent); + } +#else + ssize_t sent; + (void)qs; + (void)gsolen; + + *psent = 0; + + while((sent = send(sockfd, (const char *)pkt, pktlen, 0)) == -1 && + SOCKERRNO == EINTR) + ; + + if(sent == -1) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + return CURLE_AGAIN; + } + else { + failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO); + if(SOCKERRNO != EMSGSIZE) { + return CURLE_SEND_ERROR; + } + /* UDP datagram is too large; caused by PMTUD. Just let it be + lost. */ + } + } +#endif + + *psent = pktlen; + + return CURLE_OK; +} + +static CURLcode send_packet(size_t *psent, struct Curl_easy *data, int sockfd, + struct quicsocket *qs, const uint8_t *pkt, + size_t pktlen, size_t gsolen) +{ + if(qs->no_gso && pktlen > gsolen) { + return send_packet_no_gso(psent, data, sockfd, qs, pkt, pktlen, gsolen); + } + + return do_sendmsg(psent, data, sockfd, qs, pkt, pktlen, gsolen); +} + +static void push_blocked_pkt(struct quicsocket *qs, const uint8_t *pkt, + size_t pktlen, size_t gsolen) +{ + struct blocked_pkt *blkpkt; + + assert(qs->num_blocked_pkt < + sizeof(qs->blocked_pkt) / sizeof(qs->blocked_pkt[0])); + + blkpkt = &qs->blocked_pkt[qs->num_blocked_pkt++]; + + blkpkt->pkt = pkt; + blkpkt->pktlen = pktlen; + blkpkt->gsolen = gsolen; +} + +static CURLcode send_blocked_pkt(struct Curl_easy *data, int sockfd, + struct quicsocket *qs) +{ + size_t sent; + CURLcode curlcode; + struct blocked_pkt *blkpkt; + + for(; qs->num_blocked_pkt_sent < qs->num_blocked_pkt; + ++qs->num_blocked_pkt_sent) { + blkpkt = &qs->blocked_pkt[qs->num_blocked_pkt_sent]; + curlcode = send_packet(&sent, data, sockfd, qs, blkpkt->pkt, + blkpkt->pktlen, blkpkt->gsolen); + + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + blkpkt->pkt += sent; + blkpkt->pktlen -= sent; + } + return curlcode; + } + } + + qs->num_blocked_pkt = 0; + qs->num_blocked_pkt_sent = 0; + + return CURLE_OK; +} + static CURLcode ng_flush_egress(struct Curl_easy *data, int sockfd, struct quicsocket *qs) { int rv; - ssize_t sent; - ssize_t outlen; - uint8_t out[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; + size_t sent; + ngtcp2_ssize outlen; + uint8_t *outpos = qs->pktbuf; + size_t max_udp_payload_size = + ngtcp2_conn_get_max_udp_payload_size(qs->qconn); + size_t path_max_udp_payload_size = + ngtcp2_conn_get_path_max_udp_payload_size(qs->qconn); + size_t max_pktcnt = + CURLMIN(MAX_PKT_BURST, qs->pktbuflen / max_udp_payload_size); + size_t pktcnt = 0; + size_t gsolen; ngtcp2_path_storage ps; ngtcp2_tstamp ts = timestamp(); ngtcp2_tstamp expiry; ngtcp2_duration timeout; int64_t stream_id; - ssize_t veccnt; + nghttp3_ssize veccnt; int fin; nghttp3_vec vec[16]; - ssize_t ndatalen; + ngtcp2_ssize ndatalen; uint32_t flags; + CURLcode curlcode; rv = ngtcp2_conn_handle_expiry(qs->qconn, ts); if(rv) { failf(data, "ngtcp2_conn_handle_expiry returned error: %s", ngtcp2_strerror(rv)); + ngtcp2_connection_close_error_set_transport_error_liberr(&qs->last_error, + rv, NULL, 0); return CURLE_SEND_ERROR; } + if(qs->num_blocked_pkt) { + curlcode = send_blocked_pkt(data, sockfd, qs); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + } + ngtcp2_path_storage_zero(&ps); for(;;) { @@ -1755,17 +1855,34 @@ static CURLcode ng_flush_egress(struct Curl_easy *data, if(veccnt < 0) { failf(data, "nghttp3_conn_writev_stream returned error: %s", nghttp3_strerror((int)veccnt)); + ngtcp2_connection_close_error_set_application_error( + &qs->last_error, + nghttp3_err_infer_quic_app_error_code((int)veccnt), NULL, 0); return CURLE_SEND_ERROR; } } flags = NGTCP2_WRITE_STREAM_FLAG_MORE | (fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0); - outlen = ngtcp2_conn_writev_stream(qs->qconn, &ps.path, NULL, out, - sizeof(out), + outlen = ngtcp2_conn_writev_stream(qs->qconn, &ps.path, NULL, outpos, + max_udp_payload_size, &ndatalen, flags, stream_id, (const ngtcp2_vec *)vec, veccnt, ts); if(outlen == 0) { + if(outpos != qs->pktbuf) { + curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf, + outpos - qs->pktbuf, gsolen); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + push_blocked_pkt(qs, qs->pktbuf + sent, outpos - qs->pktbuf - sent, + gsolen); + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + } + break; } if(outlen < 0) { @@ -1802,6 +1919,8 @@ static CURLcode ng_flush_egress(struct Curl_easy *data, assert(ndatalen == -1); failf(data, "ngtcp2_conn_writev_stream returned error: %s", ngtcp2_strerror((int)outlen)); + ngtcp2_connection_close_error_set_transport_error_liberr( + &qs->last_error, (int)outlen, NULL, 0); return CURLE_SEND_ERROR; } } @@ -1814,20 +1933,61 @@ static CURLcode ng_flush_egress(struct Curl_easy *data, } } - while((sent = send(sockfd, (const char *)out, outlen, 0)) == -1 && - SOCKERRNO == EINTR) - ; + outpos += outlen; - if(sent == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { - /* TODO Cache packet */ - break; + if(pktcnt == 0) { + gsolen = outlen; + } + else if((size_t)outlen > gsolen || + (gsolen > path_max_udp_payload_size && + (size_t)outlen != gsolen)) { + /* Packet larger than path_max_udp_payload_size is PMTUD probe + packet and it might not be sent because of EMSGSIZE. Send + them separately to minimize the loss. */ + curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf, + outpos - outlen - qs->pktbuf, gsolen); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + push_blocked_pkt(qs, qs->pktbuf + sent, + outpos - outlen - qs->pktbuf - sent, gsolen); + push_blocked_pkt(qs, outpos - outlen, outlen, outlen); + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; } - else { - failf(data, "send() returned %zd (errno %d)", sent, - SOCKERRNO); - return CURLE_SEND_ERROR; + curlcode = send_packet(&sent, data, sockfd, qs, outpos - outlen, outlen, + outlen); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + assert(0 == sent); + push_blocked_pkt(qs, outpos - outlen, outlen, outlen); + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; } + + pktcnt = 0; + outpos = qs->pktbuf; + continue; + } + + if(++pktcnt >= max_pktcnt || (size_t)outlen < gsolen) { + curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf, + outpos - qs->pktbuf, gsolen); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + push_blocked_pkt(qs, qs->pktbuf + sent, outpos - qs->pktbuf - sent, + gsolen); + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + + pktcnt = 0; + outpos = qs->pktbuf; } } @@ -1894,4 +2054,26 @@ bool Curl_quic_data_pending(const struct Curl_easy *data) return Curl_dyn_len(&stream->overflow) > 0; } +/* + * Called from transfer.c:Curl_readwrite when neither HTTP level read + * nor write is performed. It is a good place to handle timer expiry + * for QUIC transport. + */ +CURLcode Curl_quic_idle(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + struct quicsocket *qs = conn->quic; + + if(ngtcp2_conn_get_expiry(qs->qconn) > timestamp()) { + return CURLE_OK; + } + + if(ng_flush_egress(data, sockfd, qs)) { + return CURLE_SEND_ERROR; + } + + return CURLE_OK; +} + #endif diff --git a/libs/libcurl/src/vquic/ngtcp2.h b/libs/libcurl/src/vquic/ngtcp2.h index 501453042b..23fbcb66df 100644 --- a/libs/libcurl/src/vquic/ngtcp2.h +++ b/libs/libcurl/src/vquic/ngtcp2.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -20,13 +20,19 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include "curl_setup.h" #ifdef USE_NGTCP2 -#include <ngtcp2/ngtcp2.h> +#ifdef HAVE_NETINET_UDP_H +#include <netinet/udp.h> +#endif + +#include <ngtcp2/ngtcp2_crypto.h> #include <nghttp3/nghttp3.h> #ifdef USE_OPENSSL #include <openssl/ssl.h> @@ -34,6 +40,12 @@ #include <gnutls/gnutls.h> #endif +struct blocked_pkt { + const uint8_t *pkt; + size_t pktlen; + size_t gsolen; +}; + struct quicsocket { struct connectdata *conn; /* point back to the connection */ ngtcp2_conn *qconn; @@ -42,6 +54,8 @@ struct quicsocket { uint32_t version; ngtcp2_settings settings; ngtcp2_transport_params transport_params; + ngtcp2_connection_close_error last_error; + ngtcp2_crypto_conn_ref conn_ref; #ifdef USE_OPENSSL SSL_CTX *sslctx; SSL *ssl; @@ -49,10 +63,17 @@ struct quicsocket { gnutls_certificate_credentials_t cred; gnutls_session_t ssl; #endif - /* the last TLS alert description generated by the local endpoint */ - uint8_t tls_alert; struct sockaddr_storage local_addr; socklen_t local_addrlen; + bool no_gso; + uint8_t *pktbuf; + size_t pktbuflen; + /* the number of entries in blocked_pkt */ + size_t num_blocked_pkt; + /* the number of processed entries in blocked_pkt */ + size_t num_blocked_pkt_sent; + /* the packets blocked by sendmsg (EAGAIN or EWOULDBLOCK) */ + struct blocked_pkt blocked_pkt[2]; nghttp3_conn *h3conn; nghttp3_settings h3settings; diff --git a/libs/libcurl/src/vquic/quiche.c b/libs/libcurl/src/vquic/quiche.c index bfdc966a85..9a2b74310a 100644 --- a/libs/libcurl/src/vquic/quiche.c +++ b/libs/libcurl/src/vquic/quiche.c @@ -18,6 +18,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include "curl_setup.h" @@ -201,23 +203,31 @@ static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data) { struct connectdata *conn = data->conn; - const char * const ssl_cafile = conn->ssl_config.CAfile; - const char * const ssl_capath = conn->ssl_config.CApath; - if(conn->ssl_config.verifypeer) { - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); - /* tell OpenSSL where to find CA certificates that are used to verify - the server's certificate. */ - if(!SSL_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; + const char * const ssl_cafile = conn->ssl_config.CAfile; + const char * const ssl_capath = conn->ssl_config.CApath; + if(ssl_cafile || ssl_capath) { + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + /* tell OpenSSL where to find CA certificates that are used to verify + the server's certificate. */ + if(!SSL_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"); } - 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 openssl's built-in default as fallback */ + SSL_CTX_set_default_verify_paths(ssl_ctx); + } +#endif } } return ssl_ctx; @@ -856,4 +866,15 @@ bool Curl_quic_data_pending(const struct Curl_easy *data) return FALSE; } +/* + * Called from transfer.c:Curl_readwrite when neither HTTP level read + * nor write is performed. It is a good place to handle timer expiry + * for QUIC transport. + */ +CURLcode Curl_quic_idle(struct Curl_easy *data) +{ + (void)data; + return CURLE_OK; +} + #endif diff --git a/libs/libcurl/src/vquic/quiche.h b/libs/libcurl/src/vquic/quiche.h index 759a20bbac..de68089451 100644 --- a/libs/libcurl/src/vquic/quiche.h +++ b/libs/libcurl/src/vquic/quiche.h @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include "curl_setup.h" diff --git a/libs/libcurl/src/vquic/vquic.c b/libs/libcurl/src/vquic/vquic.c index be2a65f454..e52a4f301d 100644 --- a/libs/libcurl/src/vquic/vquic.c +++ b/libs/libcurl/src/vquic/vquic.c @@ -18,6 +18,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include "curl_setup.h" diff --git a/libs/libcurl/src/vquic/vquic.h b/libs/libcurl/src/vquic/vquic.h index 3df138f10e..8f599a8f49 100644 --- a/libs/libcurl/src/vquic/vquic.h +++ b/libs/libcurl/src/vquic/vquic.h @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include "curl_setup.h" |