diff options
Diffstat (limited to 'Utilities/cmcurl/lib/vquic/ngtcp2.c')
-rw-r--r-- | Utilities/cmcurl/lib/vquic/ngtcp2.c | 263 |
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); } } |