summaryrefslogtreecommitdiff
path: root/libs/libcurl/src/vquic
diff options
context:
space:
mode:
Diffstat (limited to 'libs/libcurl/src/vquic')
-rw-r--r--libs/libcurl/src/vquic/msh3.c14
-rw-r--r--libs/libcurl/src/vquic/msh3.h2
-rw-r--r--libs/libcurl/src/vquic/ngtcp2.c624
-rw-r--r--libs/libcurl/src/vquic/ngtcp2.h29
-rw-r--r--libs/libcurl/src/vquic/quiche.c51
-rw-r--r--libs/libcurl/src/vquic/quiche.h2
-rw-r--r--libs/libcurl/src/vquic/vquic.c2
-rw-r--r--libs/libcurl/src/vquic/vquic.h2
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(
- &params, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS,
- data, data_size) != 0)
- return -1;
-
- if(ngtcp2_conn_set_remote_transport_params(qs->qconn, &params) != 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, &params);
- nwrite = ngtcp2_encode_transport_params(
- paramsbuf, sizeof(paramsbuf), NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO,
- &params);
- 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"