summaryrefslogtreecommitdiffstats
path: root/Utilities/cmcurl/lib/vquic/ngtcp2.c
diff options
context:
space:
mode:
Diffstat (limited to 'Utilities/cmcurl/lib/vquic/ngtcp2.c')
-rw-r--r--Utilities/cmcurl/lib/vquic/ngtcp2.c263
1 files changed, 99 insertions, 164 deletions
diff --git a/Utilities/cmcurl/lib/vquic/ngtcp2.c b/Utilities/cmcurl/lib/vquic/ngtcp2.c
index 1596049..abce631 100644
--- a/Utilities/cmcurl/lib/vquic/ngtcp2.c
+++ b/Utilities/cmcurl/lib/vquic/ngtcp2.c
@@ -5,7 +5,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
@@ -45,7 +45,9 @@
#include "strerror.h"
#include "dynbuf.h"
#include "vquic.h"
+#include "h2h3.h"
#include "vtls/keylog.h"
+#include "vtls/vtls.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -71,7 +73,7 @@
* the far end, then start over at index 0 again.
*/
-#define H3_SEND_SIZE (20*1024)
+#define H3_SEND_SIZE (256*1024)
struct h3out {
uint8_t buf[H3_SEND_SIZE];
size_t used; /* number of bytes used in the buffer */
@@ -81,7 +83,7 @@ struct h3out {
#define QUIC_MAX_STREAMS (256*1024)
#define QUIC_MAX_DATA (1*1024*1024)
-#define QUIC_IDLE_TIMEOUT 60000 /* milliseconds */
+#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS)
#ifdef USE_OPENSSL
#define QUIC_CIPHERS \
@@ -313,6 +315,25 @@ static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
return ssl_ctx;
}
+static CURLcode quic_set_client_cert(struct Curl_easy *data,
+ struct quicsocket *qs)
+{
+ struct connectdata *conn = data->conn;
+ SSL_CTX *ssl_ctx = qs->sslctx;
+ char *const ssl_cert = SSL_SET_OPTION(primary.clientcert);
+ const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob);
+ const char *const ssl_cert_type = SSL_SET_OPTION(cert_type);
+
+ if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
+ return Curl_ossl_set_client_cert(
+ data, ssl_ctx, ssl_cert, ssl_cert_blob, ssl_cert_type,
+ SSL_SET_OPTION(key), SSL_SET_OPTION(key_blob),
+ SSL_SET_OPTION(key_type), SSL_SET_OPTION(key_passwd));
+ }
+
+ return CURLE_OK;
+}
+
/** SSL callbacks ***/
static int quic_init_ssl(struct quicsocket *qs)
@@ -743,7 +764,8 @@ static ngtcp2_callbacks ng_callbacks = {
NULL, /* ack_datagram */
NULL, /* lost_datagram */
ngtcp2_crypto_get_path_challenge_data_cb,
- cb_stream_stop_sending
+ cb_stream_stop_sending,
+ NULL, /* version_negotiation */
};
/*
@@ -785,6 +807,10 @@ CURLcode Curl_quic_connect(struct Curl_easy *data,
qs->sslctx = quic_ssl_ctx(data);
if(!qs->sslctx)
return CURLE_QUIC_CONNECT_ERROR;
+
+ result = quic_set_client_cert(data, qs);
+ if(result)
+ return result;
#endif
if(quic_init_ssl(qs))
@@ -841,6 +867,8 @@ static int ng_getsock(struct Curl_easy *data, struct connectdata *conn,
{
struct SingleRequest *k = &data->req;
int bitmap = GETSOCK_BLANK;
+ struct HTTP *stream = data->req.p.http;
+ struct quicsocket *qs = conn->quic;
socks[0] = conn->sock[FIRSTSOCKET];
@@ -849,7 +877,11 @@ static int ng_getsock(struct Curl_easy *data, struct connectdata *conn,
bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
/* we're still uploading or the HTTP/2 layer wants to send data */
- if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND)
+ if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND &&
+ (!stream->h3out || stream->h3out->used < H3_SEND_SIZE) &&
+ ngtcp2_conn_get_cwnd_left(qs->qconn) &&
+ ngtcp2_conn_get_max_data_left(qs->qconn) &&
+ nghttp3_conn_is_stream_writable(qs->h3conn, stream->stream3_id))
bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
return bitmap;
@@ -857,8 +889,26 @@ static int ng_getsock(struct Curl_easy *data, struct connectdata *conn,
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);
+ if(rc > 0) {
+ while((send(qs->conn->sock[FIRSTSOCKET], buffer, rc, 0) == -1) &&
+ SOCKERRNO == EINTR);
+ }
+
qs->conn = NULL;
if(qs->qlogfd != -1) {
close(qs->qlogfd);
@@ -1043,7 +1093,7 @@ static int decode_status_code(const uint8_t *value, size_t len)
}
static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
- void *user_data, void *stream_user_data)
+ int fin, void *user_data, void *stream_user_data)
{
struct Curl_easy *data = stream_user_data;
struct HTTP *stream = data->req.p.http;
@@ -1051,6 +1101,7 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
(void)conn;
(void)stream_id;
(void)user_data;
+ (void)fin;
/* add a CRLF only if we've received some headers */
if(stream->firstheader) {
@@ -1078,8 +1129,7 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
(void)flags;
(void)user_data;
- if(h3name.len == sizeof(":status") - 1 &&
- !memcmp(":status", h3name.base, h3name.len)) {
+ 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);
@@ -1218,6 +1268,8 @@ static size_t drain_overflow_buffer(struct HTTP *stream)
if(ncopy != overlen)
/* make the buffer only keep the tail */
(void)Curl_dyn_tail(&stream->overflow, overlen - ncopy);
+ else
+ Curl_dyn_reset(&stream->overflow);
}
return ncopy;
}
@@ -1329,6 +1381,10 @@ static ssize_t cb_h3_readfunction(nghttp3_conn *conn, int64_t stream_id,
return 1;
}
+ if(stream->upload_len && H3_SEND_SIZE <= stream->h3out->used) {
+ return NGHTTP3_ERR_WOULDBLOCK;
+ }
+
nread = CURLMIN(stream->upload_len, H3_SEND_SIZE - stream->h3out->used);
if(nread > 0) {
/* nghttp3 wants us to hold on to the data until it tells us it is okay to
@@ -1363,7 +1419,7 @@ static ssize_t cb_h3_readfunction(nghttp3_conn *conn, int64_t stream_id,
}
if(stream->upload_done && !stream->upload_len &&
(stream->upload_left <= 0)) {
- H3BUGF(infof(data, "!!!!!!!!! cb_h3_readfunction sets EOF"));
+ H3BUGF(infof(data, "cb_h3_readfunction sets EOF"));
*pflags = NGHTTP3_DATA_FLAG_EOF;
return nread ? 1 : 0;
}
@@ -1383,16 +1439,13 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
struct connectdata *conn = data->conn;
struct HTTP *stream = data->req.p.http;
size_t nheader;
- size_t i;
- size_t authority_idx;
- char *hdbuf = (char *)mem;
- char *end, *line_end;
struct quicsocket *qs = conn->quic;
CURLcode result = CURLE_OK;
nghttp3_nv *nva = NULL;
int64_t stream3_id;
int rc;
struct h3out *h3out = NULL;
+ struct h2h3req *hreq = NULL;
rc = ngtcp2_conn_open_bidi_stream(qs->qconn, &stream3_id, NULL);
if(rc) {
@@ -1405,158 +1458,23 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
stream->h3req = TRUE; /* senf off! */
Curl_dyn_init(&stream->overflow, CURL_MAX_READ_SIZE);
- /* Calculate number of headers contained in [mem, mem + len). Assumes a
- correctly generated HTTP header field block. */
- nheader = 0;
- for(i = 1; i < len; ++i) {
- if(hdbuf[i] == '\n' && hdbuf[i - 1] == '\r') {
- ++nheader;
- ++i;
- }
- }
- if(nheader < 2)
+ result = Curl_pseudo_headers(data, mem, len, &hreq);
+ if(result)
goto fail;
+ nheader = hreq->entries;
- /* We counted additional 2 \r\n in the first and last line. We need 3
- new headers: :method, :path and :scheme. Therefore we need one
- more space. */
- nheader += 1;
nva = malloc(sizeof(nghttp3_nv) * nheader);
if(!nva) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
-
- /* Extract :method, :path from request line
- We do line endings with CRLF so checking for CR is enough */
- line_end = memchr(hdbuf, '\r', len);
- if(!line_end) {
- result = CURLE_BAD_FUNCTION_ARGUMENT; /* internal error */
- goto fail;
- }
-
- /* Method does not contain spaces */
- end = memchr(hdbuf, ' ', line_end - hdbuf);
- if(!end || end == hdbuf)
- goto fail;
- nva[0].name = (unsigned char *)":method";
- nva[0].namelen = strlen((char *)nva[0].name);
- nva[0].value = (unsigned char *)hdbuf;
- nva[0].valuelen = (size_t)(end - hdbuf);
- nva[0].flags = NGHTTP3_NV_FLAG_NONE;
-
- hdbuf = end + 1;
-
- /* Path may contain spaces so scan backwards */
- end = NULL;
- for(i = (size_t)(line_end - hdbuf); i; --i) {
- if(hdbuf[i - 1] == ' ') {
- end = &hdbuf[i - 1];
- break;
- }
- }
- if(!end || end == hdbuf)
- goto fail;
- nva[1].name = (unsigned char *)":path";
- nva[1].namelen = strlen((char *)nva[1].name);
- nva[1].value = (unsigned char *)hdbuf;
- nva[1].valuelen = (size_t)(end - hdbuf);
- nva[1].flags = NGHTTP3_NV_FLAG_NONE;
-
- nva[2].name = (unsigned char *)":scheme";
- nva[2].namelen = strlen((char *)nva[2].name);
- if(conn->handler->flags & PROTOPT_SSL)
- nva[2].value = (unsigned char *)"https";
- else
- nva[2].value = (unsigned char *)"http";
- nva[2].valuelen = strlen((char *)nva[2].value);
- nva[2].flags = NGHTTP3_NV_FLAG_NONE;
-
-
- authority_idx = 0;
- i = 3;
- while(i < nheader) {
- size_t hlen;
-
- hdbuf = line_end + 2;
-
- /* check for next CR, but only within the piece of data left in the given
- buffer */
- line_end = memchr(hdbuf, '\r', len - (hdbuf - (char *)mem));
- if(!line_end || (line_end == hdbuf))
- goto fail;
-
- /* header continuation lines are not supported */
- if(*hdbuf == ' ' || *hdbuf == '\t')
- goto fail;
-
- for(end = hdbuf; end < line_end && *end != ':'; ++end)
- ;
- if(end == hdbuf || end == line_end)
- goto fail;
- hlen = end - hdbuf;
-
- if(hlen == 4 && strncasecompare("host", hdbuf, 4)) {
- authority_idx = i;
- nva[i].name = (unsigned char *)":authority";
- nva[i].namelen = strlen((char *)nva[i].name);
- }
- else {
- nva[i].namelen = (size_t)(end - hdbuf);
- /* Lower case the header name for HTTP/3 */
- Curl_strntolower((char *)hdbuf, hdbuf, nva[i].namelen);
- nva[i].name = (unsigned char *)hdbuf;
- }
- nva[i].flags = NGHTTP3_NV_FLAG_NONE;
- hdbuf = end + 1;
- while(*hdbuf == ' ' || *hdbuf == '\t')
- ++hdbuf;
- end = line_end;
-
-#if 0 /* This should probably go in more or less like this */
- switch(inspect_header((const char *)nva[i].name, nva[i].namelen, hdbuf,
- end - hdbuf)) {
- case HEADERINST_IGNORE:
- /* skip header fields prohibited by HTTP/2 specification. */
- --nheader;
- continue;
- case HEADERINST_TE_TRAILERS:
- nva[i].value = (uint8_t*)"trailers";
- nva[i].value_len = sizeof("trailers") - 1;
- break;
- default:
- nva[i].value = (unsigned char *)hdbuf;
- nva[i].value_len = (size_t)(end - hdbuf);
- }
-#endif
- nva[i].value = (unsigned char *)hdbuf;
- nva[i].valuelen = (size_t)(end - hdbuf);
- nva[i].flags = NGHTTP3_NV_FLAG_NONE;
-
- ++i;
- }
-
- /* :authority must come before non-pseudo header fields */
- if(authority_idx && authority_idx != AUTHORITY_DST_IDX) {
- nghttp3_nv authority = nva[authority_idx];
- for(i = authority_idx; i > AUTHORITY_DST_IDX; --i) {
- nva[i] = nva[i - 1];
- }
- nva[i] = authority;
- }
-
- /* Warn stream may be rejected if cumulative length of headers is too
- large. */
-#define MAX_ACC 60000 /* <64KB to account for some overhead */
- {
- size_t acc = 0;
- for(i = 0; i < nheader; ++i)
- acc += nva[i].namelen + nva[i].valuelen;
-
- if(acc > MAX_ACC) {
- infof(data, "http_request: Warning: The cumulative length of all "
- "headers exceeds %d bytes and that could cause the "
- "stream to be rejected.", MAX_ACC);
+ else {
+ unsigned int i;
+ for(i = 0; i < nheader; i++) {
+ nva[i].name = (unsigned char *)hreq->header[i].name;
+ nva[i].namelen = hreq->header[i].namelen;
+ nva[i].value = (unsigned char *)hreq->header[i].value;
+ nva[i].valuelen = hreq->header[i].valuelen;
}
}
@@ -1605,10 +1523,12 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)",
stream3_id, (void *)data);
+ Curl_pseudo_free(hreq);
return CURLE_OK;
fail:
free(nva);
+ Curl_pseudo_free(hreq);
return result;
}
static ssize_t ngh3_stream_send(struct Curl_easy *data,
@@ -1617,7 +1537,7 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data,
size_t len,
CURLcode *curlcode)
{
- ssize_t sent;
+ ssize_t sent = 0;
struct connectdata *conn = data->conn;
struct quicsocket *qs = conn->quic;
curl_socket_t sockfd = conn->sock[sockindex];
@@ -1629,6 +1549,9 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data,
*curlcode = CURLE_SEND_ERROR;
return -1;
}
+ /* Assume that mem of length len only includes HTTP/1.1 style
+ header fields. In other words, it does not contain request
+ body. */
sent = len;
}
else {
@@ -1638,7 +1561,6 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data,
stream->upload_mem = mem;
stream->upload_len = len;
(void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id);
- sent = len;
}
else {
*curlcode = CURLE_AGAIN;
@@ -1653,8 +1575,20 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data,
/* Reset post upload buffer after resumed. */
if(stream->upload_mem) {
+ if(data->set.postfields) {
+ sent = len;
+ }
+ else {
+ sent = len - stream->upload_len;
+ }
+
stream->upload_mem = NULL;
stream->upload_len = 0;
+
+ if(sent == 0) {
+ *curlcode = CURLE_AGAIN;
+ return -1;
+ }
}
*curlcode = CURLE_OK;
@@ -1676,7 +1610,6 @@ static CURLcode ng_has_connected(struct Curl_easy *data,
if(conn->ssl_config.verifyhost) {
#ifdef USE_OPENSSL
X509 *server_cert;
- CURLcode result;
server_cert = SSL_get_peer_certificate(conn->quic->ssl);
if(!server_cert) {
return CURLE_PEER_FAILED_VERIFICATION;
@@ -1786,7 +1719,6 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
uint8_t out[NGTCP2_MAX_UDP_PAYLOAD_SIZE];
ngtcp2_path_storage ps;
ngtcp2_tstamp ts = timestamp();
- struct sockaddr_storage remote_addr;
ngtcp2_tstamp expiry;
ngtcp2_duration timeout;
int64_t stream_id;
@@ -1875,7 +1807,6 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
}
}
- memcpy(&remote_addr, ps.path.remote.addr, ps.path.remote.addrlen);
while((sent = send(sockfd, (const char *)out, outlen, 0)) == -1 &&
SOCKERRNO == EINTR)
;
@@ -1896,10 +1827,13 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
expiry = ngtcp2_conn_get_expiry(qs->qconn);
if(expiry != UINT64_MAX) {
if(expiry <= ts) {
- timeout = NGTCP2_MILLISECONDS;
+ timeout = 0;
}
else {
timeout = expiry - ts;
+ if(timeout % NGTCP2_MILLISECONDS) {
+ timeout += NGTCP2_MILLISECONDS;
+ }
}
Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC);
}
@@ -1935,6 +1869,7 @@ void Curl_quic_done(struct Curl_easy *data, bool premature)
/* only for HTTP/3 transfers */
struct HTTP *stream = data->req.p.http;
Curl_dyn_free(&stream->overflow);
+ free(stream->h3out);
}
}