summaryrefslogtreecommitdiff
path: root/libs/libcurl/src/vquic/ngtcp2.c
diff options
context:
space:
mode:
authordartraiden <dartraiden@protonmail.com>2020-01-12 14:12:42 +0300
committerdartraiden <dartraiden@protonmail.com>2020-01-12 14:14:27 +0300
commit69d3b201c14db069ad0aef4b21e8efb45e21df9b (patch)
tree9c168030e03459dfe2d2d6cf64511321786b41f8 /libs/libcurl/src/vquic/ngtcp2.c
parentdcb62e4830223c1f5233d90b855e74006fd0942b (diff)
libcurl: update to 7.68
Diffstat (limited to 'libs/libcurl/src/vquic/ngtcp2.c')
-rw-r--r--libs/libcurl/src/vquic/ngtcp2.c278
1 files changed, 225 insertions, 53 deletions
diff --git a/libs/libcurl/src/vquic/ngtcp2.c b/libs/libcurl/src/vquic/ngtcp2.c
index c0f9b16e38..e97e9e871b 100644
--- a/libs/libcurl/src/vquic/ngtcp2.c
+++ b/libs/libcurl/src/vquic/ngtcp2.c
@@ -49,7 +49,7 @@
#ifdef DEBUG_HTTP3
#define H3BUGF(x) x
#else
-#define H3BUGF(x) do { } WHILE_FALSE
+#define H3BUGF(x) do { } while(0)
#endif
/*
@@ -174,8 +174,10 @@ static int quic_set_encryption_secrets(SSL *ssl,
tx_secret, secretlen, NGTCP2_CRYPTO_SIDE_CLIENT) != 0)
return 0;
- if(level == NGTCP2_CRYPTO_LEVEL_APP && init_ngh3_conn(qs) != CURLE_OK)
- return 0;
+ if(level == NGTCP2_CRYPTO_LEVEL_APP) {
+ if(init_ngh3_conn(qs) != CURLE_OK)
+ return 0;
+ }
return 1;
}
@@ -188,11 +190,12 @@ static int quic_add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level,
ngtcp2_crypto_level level = quic_from_ossl_level(ossl_level);
int rv;
- crypto_data = &qs->client_crypto_data[level];
+ crypto_data = &qs->crypto_data[level];
if(crypto_data->buf == NULL) {
crypto_data->buf = malloc(4096);
+ if(!crypto_data->buf)
+ return 0;
crypto_data->alloclen = 4096;
- /* TODO Explode if malloc failed */
}
/* TODO Just pretend that handshake does not grow more than 4KiB for
@@ -203,8 +206,8 @@ static int quic_add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level,
crypto_data->len += len;
rv = ngtcp2_conn_submit_crypto_data(
- qs->qconn, level, (uint8_t *)(&crypto_data->buf[crypto_data->len] - len),
- len);
+ qs->qconn, level, (uint8_t *)(&crypto_data->buf[crypto_data->len] - len),
+ len);
if(rv) {
H3BUGF(fprintf(stderr, "write_client_handshake failed\n"));
}
@@ -244,8 +247,9 @@ static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
SSL_CTX_set_default_verify_paths(ssl_ctx);
if(SSL_CTX_set_ciphersuites(ssl_ctx, QUIC_CIPHERS) != 1) {
- failf(data, "SSL_CTX_set_ciphersuites: %s",
- ERR_error_string(ERR_get_error(), NULL));
+ 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;
}
@@ -305,7 +309,7 @@ static int cb_initial(ngtcp2_conn *quic, void *user_data)
struct quicsocket *qs = (struct quicsocket *)user_data;
if(ngtcp2_crypto_read_write_crypto_data(
- quic, qs->ssl, NGTCP2_CRYPTO_LEVEL_INITIAL, NULL, 0) != 0)
+ quic, qs->ssl, NGTCP2_CRYPTO_LEVEL_INITIAL, NULL, 0) != 0)
return NGTCP2_ERR_CALLBACK_FAILURE;
return 0;
@@ -336,6 +340,16 @@ static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data)
return 0;
}
+static void extend_stream_window(ngtcp2_conn *tconn,
+ struct HTTP *stream)
+{
+ size_t thismuch = stream->unacked_window;
+ ngtcp2_conn_extend_max_stream_offset(tconn, stream->stream3_id, thismuch);
+ ngtcp2_conn_extend_max_offset(tconn, thismuch);
+ stream->unacked_window = 0;
+}
+
+
static int cb_recv_stream_data(ngtcp2_conn *tconn, int64_t stream_id,
int fin, uint64_t offset,
const uint8_t *buf, size_t buflen,
@@ -346,9 +360,6 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, int64_t stream_id,
(void)offset;
(void)stream_user_data;
- infof(qs->conn->data, "Received %ld bytes data on stream %u\n",
- buflen, stream_id);
-
nconsumed =
nghttp3_conn_read_stream(qs->h3conn, stream_id, buf, buflen, fin);
if(nconsumed < 0) {
@@ -357,6 +368,9 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, int64_t stream_id,
return NGTCP2_ERR_CALLBACK_FAILURE;
}
+ /* number of bytes inside buflen which consists of framing overhead
+ * including QPACK HEADERS. In other words, it does not consume payload of
+ * DATA frame. */
ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, nconsumed);
ngtcp2_conn_extend_max_offset(tconn, nconsumed);
@@ -514,7 +528,7 @@ static ngtcp2_conn_callbacks ng_callbacks = {
NULL, /* rand */
cb_get_new_connection_id,
NULL, /* remove_connection_id */
- NULL, /* update_key */
+ ngtcp2_crypto_update_key_cb, /* update_key */
NULL, /* path_validation */
NULL, /* select_preferred_addr */
cb_stream_reset,
@@ -656,8 +670,16 @@ static int ng_perform_getsock(const struct connectdata *conn,
static CURLcode ng_disconnect(struct connectdata *conn,
bool dead_connection)
{
- (void)conn;
+ int i;
+ struct quicsocket *qs = &conn->hequic[0];
(void)dead_connection;
+ if(qs->ssl)
+ SSL_free(qs->ssl);
+ for(i = 0; i < 3; i++)
+ free(qs->crypto_data[i].buf);
+ nghttp3_conn_del(qs->h3conn);
+ ngtcp2_conn_del(qs->qconn);
+ SSL_CTX_free(qs->sslctx);
return CURLE_OK;
}
@@ -704,42 +726,121 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id,
stream->closed = TRUE;
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. */
+ data->state.drain = 1;
return 0;
}
-static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream_id,
- const uint8_t *buf, size_t buflen,
- void *user_data, void *stream_user_data)
-{
- struct quicsocket *qs = user_data;
- size_t ncopy;
- struct Curl_easy *data = stream_user_data;
- struct HTTP *stream = data->req.protop;
- (void)conn;
- H3BUGF(infof(data, "cb_h3_recv_data CALLED with %d bytes\n", buflen));
+/* Minimum size of the overflow buffer */
+#define OVERFLOWSIZE 1024
- /* TODO: this needs to be handled properly */
- DEBUGASSERT(buflen <= stream->len);
+/*
+ * allocate_overflow() ensures that there is room for incoming data in the
+ * overflow buffer, growing it to accommodate the new data if necessary. We
+ * may need to use the overflow buffer because we can't precisely limit the
+ * amount of HTTP/3 header data we receive using QUIC flow control mechanisms.
+ */
+static CURLcode allocate_overflow(struct Curl_easy *data,
+ struct HTTP *stream,
+ size_t length)
+{
+ size_t maxleft;
+ size_t newsize;
+ /* length can be arbitrarily large, so take care not to overflow newsize */
+ maxleft = CURL_MAX_READ_SIZE - stream->overflow_buflen;
+ if(length > maxleft) {
+ /* The reason to have a max limit for this is to avoid the risk of a bad
+ server feeding libcurl with a highly compressed list of headers that
+ will cause our overflow buffer to grow too large */
+ failf(data, "Rejected %zu bytes of overflow data (max is %d)!",
+ stream->overflow_buflen + length, CURL_MAX_READ_SIZE);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ newsize = stream->overflow_buflen + length;
+ if(newsize > stream->overflow_bufsize) {
+ /* We enlarge the overflow buffer as it is too small */
+ char *newbuff;
+ newsize = CURLMAX(newsize * 3 / 2, stream->overflow_bufsize*2);
+ newsize = CURLMIN(CURLMAX(OVERFLOWSIZE, newsize), CURL_MAX_READ_SIZE);
+ newbuff = realloc(stream->overflow_buf, newsize);
+ if(!newbuff) {
+ failf(data, "Failed to alloc memory for overflow buffer!");
+ return CURLE_OUT_OF_MEMORY;
+ }
+ stream->overflow_buf = newbuff;
+ stream->overflow_bufsize = newsize;
+ infof(data, "Grew HTTP/3 overflow buffer to %zu bytes\n", newsize);
+ }
+ return CURLE_OK;
+}
- ncopy = CURLMIN(stream->len, buflen);
- memcpy(stream->mem, buf, ncopy);
- stream->len -= ncopy;
- stream->memlen += ncopy;
+/*
+ * write_data() copies data to the stream's receive buffer. If not enough
+ * space is available in the receive buffer, it copies the rest to the
+ * stream's overflow buffer.
+ */
+static CURLcode write_data(struct Curl_easy *data,
+ struct HTTP *stream,
+ const void *mem, size_t memlen)
+{
+ CURLcode result = CURLE_OK;
+ const char *buf = mem;
+ size_t ncopy = memlen;
+ /* copy as much as possible to the receive buffer */
+ if(stream->len) {
+ size_t len = CURLMIN(ncopy, stream->len);
+#if 0 /* extra debugging of incoming h3 data */
+ fprintf(stderr, "!! Copies %zd bytes to %p (total %zd)\n",
+ len, stream->mem, stream->memlen);
+#endif
+ memcpy(stream->mem, buf, len);
+ stream->len -= len;
+ stream->memlen += len;
+ stream->mem += len;
+ buf += len;
+ ncopy -= len;
+ }
+ /* copy the rest to the overflow buffer */
+ if(ncopy) {
+ result = allocate_overflow(data, stream, ncopy);
+ if(result) {
+ return result;
+ }
+#if 0 /* extra debugging of incoming h3 data */
+ fprintf(stderr, "!! Copies %zd overflow bytes to %p (total %zd)\n",
+ ncopy, stream->overflow_buf, stream->overflow_buflen);
+#endif
+ memcpy(stream->overflow_buf + stream->overflow_buflen, buf, ncopy);
+ stream->overflow_buflen += ncopy;
+ }
#if 0 /* extra debugging of incoming h3 data */
- fprintf(stderr, "!! Copies %zd bytes to %p (total %zd)\n",
- ncopy, stream->mem, stream->memlen);
{
size_t i;
- for(i = 0; i < ncopy; i++) {
+ for(i = 0; i < memlen; i++) {
fprintf(stderr, "!! data[%d]: %02x '%c'\n", i, buf[i], buf[i]);
}
}
#endif
- stream->mem += ncopy;
+ return result;
+}
- ngtcp2_conn_extend_max_stream_offset(qs->qconn, stream_id, buflen);
- ngtcp2_conn_extend_max_offset(qs->qconn, buflen);
+static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream_id,
+ const uint8_t *buf, size_t buflen,
+ void *user_data, void *stream_user_data)
+{
+ struct Curl_easy *data = stream_user_data;
+ struct HTTP *stream = data->req.protop;
+ CURLcode result = CURLE_OK;
+ (void)conn;
+ result = write_data(data, stream, buf, buflen);
+ if(result) {
+ return -1;
+ }
+ stream->unacked_window += buflen;
+ (void)stream_id;
+ (void)user_data;
return 0;
}
@@ -750,10 +851,10 @@ static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
struct quicsocket *qs = user_data;
(void)conn;
(void)stream_user_data;
+ (void)stream_id;
ngtcp2_conn_extend_max_stream_offset(qs->qconn, stream_id, consumed);
ngtcp2_conn_extend_max_offset(qs->qconn, consumed);
-
return 0;
}
@@ -789,15 +890,17 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
{
struct Curl_easy *data = stream_user_data;
struct HTTP *stream = data->req.protop;
+ CURLcode result = CURLE_OK;
(void)conn;
(void)stream_id;
(void)user_data;
- if(stream->memlen >= 2) {
- memcpy(stream->mem, "\r\n", 2);
- stream->len -= 2;
- stream->memlen += 2;
- stream->mem += 2;
+ /* add a CRLF only if we've received some headers */
+ if(stream->firstheader) {
+ result = write_data(data, stream, "\r\n", 2);
+ if(result) {
+ return -1;
+ }
}
return 0;
}
@@ -811,7 +914,7 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value);
struct Curl_easy *data = stream_user_data;
struct HTTP *stream = data->req.protop;
- size_t ncopy;
+ CURLcode result = CURLE_OK;
(void)conn;
(void)stream_id;
(void)token;
@@ -820,20 +923,37 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
if(h3name.len == sizeof(":status") - 1 &&
!memcmp(":status", h3name.base, h3name.len)) {
+ 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);
- msnprintf(stream->mem, stream->len, "HTTP/3 %03d \r\n", status);
+ ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", status);
+ result = write_data(data, stream, line, ncopy);
+ if(result) {
+ return -1;
+ }
}
else {
/* store as a HTTP1-style header */
- msnprintf(stream->mem, stream->len, "%.*s: %.*s\n",
- h3name.len, h3name.base, h3val.len, h3val.base);
+ result = write_data(data, stream, h3name.base, h3name.len);
+ if(result) {
+ return -1;
+ }
+ result = write_data(data, stream, ": ", 2);
+ if(result) {
+ return -1;
+ }
+ result = write_data(data, stream, h3val.base, h3val.len);
+ if(result) {
+ return -1;
+ }
+ result = write_data(data, stream, "\r\n", 2);
+ if(result) {
+ return -1;
+ }
}
- ncopy = strlen(stream->mem);
- stream->len -= ncopy;
- stream->memlen += ncopy;
- stream->mem += ncopy;
+ stream->firstheader = TRUE;
return 0;
}
@@ -933,6 +1053,21 @@ static int init_ngh3_conn(struct quicsocket *qs)
static Curl_recv ngh3_stream_recv;
static Curl_send ngh3_stream_send;
+static size_t drain_overflow_buffer(struct HTTP *stream)
+{
+ size_t ncopy = CURLMIN(stream->overflow_buflen, stream->len);
+ if(ncopy > 0) {
+ memcpy(stream->mem, stream->overflow_buf, ncopy);
+ stream->len -= ncopy;
+ stream->mem += ncopy;
+ stream->memlen += ncopy;
+ stream->overflow_buflen -= ncopy;
+ memmove(stream->overflow_buf, stream->overflow_buf + ncopy,
+ stream->overflow_buflen);
+ }
+ return ncopy;
+}
+
/* incoming data frames on the h3 stream */
static ssize_t ngh3_stream_recv(struct connectdata *conn,
int sockindex,
@@ -952,6 +1087,10 @@ static ssize_t ngh3_stream_recv(struct connectdata *conn,
}
/* else, there's data in the buffer already */
+ /* if there's data in the overflow buffer from a previous call, copy as much
+ as possible to the receive buffer before receiving more */
+ drain_overflow_buffer(stream);
+
if(ng_process_ingress(conn, sockfd, qs)) {
*curlcode = CURLE_RECV_ERROR;
return -1;
@@ -969,8 +1108,13 @@ static ssize_t ngh3_stream_recv(struct connectdata *conn,
stream->memlen = 0;
stream->mem = buf;
stream->len = buffersize;
- H3BUGF(infof(conn->data, "!! ngh3_stream_recv returns %zd bytes at %p\n",
- memlen, buf));
+ /* extend the stream window with the data we're consuming and send out
+ any additional packets to tell the server that we can receive more */
+ extend_stream_window(qs->qconn, stream);
+ if(ng_flush_egress(conn, sockfd, qs)) {
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+ }
return memlen;
}
@@ -1590,4 +1734,32 @@ CURLcode Curl_quic_done_sending(struct connectdata *conn)
return CURLE_OK;
}
+
+/*
+ * Called from http.c:Curl_http_done when a request completes.
+ */
+void Curl_quic_done(struct Curl_easy *data, bool premature)
+{
+ (void)premature;
+ if(data->conn->handler == &Curl_handler_http3) {
+ /* only for HTTP/3 transfers */
+ struct HTTP *stream = data->req.protop;
+ Curl_safefree(stream->overflow_buf);
+ }
+}
+
+/*
+ * Called from transfer.c:data_pending to know if we should keep looping
+ * to receive more data from the connection.
+ */
+bool Curl_quic_data_pending(const struct Curl_easy *data)
+{
+ /* We may have received more data than we're able to hold in the receive
+ buffer and allocated an overflow buffer. Since it's possible that
+ there's no more data coming on the socket, we need to keep reading
+ until the overflow buffer is empty. */
+ const struct HTTP *stream = data->req.protop;
+ return stream->overflow_buflen > 0;
+}
+
#endif