diff options
| author | dartraiden <wowemuh@gmail.com> | 2022-12-22 16:31:20 +0300 |
|---|---|---|
| committer | dartraiden <wowemuh@gmail.com> | 2022-12-22 16:31:20 +0300 |
| commit | 47b6881fe726c904f87aa4be059b730ef77954d0 (patch) | |
| tree | aa72b213043d3b514c883bc59a0fa0c434106156 /libs/libcurl/src/vquic | |
| parent | 8fbc9220b7f3d3a04bbe45d32489ef882821558e (diff) | |
libcurl: update to 7.87.0
Diffstat (limited to 'libs/libcurl/src/vquic')
| -rw-r--r-- | libs/libcurl/src/vquic/msh3.c | 1054 | ||||
| -rw-r--r-- | libs/libcurl/src/vquic/msh3.h | 80 | ||||
| -rw-r--r-- | libs/libcurl/src/vquic/ngtcp2.c | 4520 | ||||
| -rw-r--r-- | libs/libcurl/src/vquic/ngtcp2.h | 187 | ||||
| -rw-r--r-- | libs/libcurl/src/vquic/quiche.c | 1787 | ||||
| -rw-r--r-- | libs/libcurl/src/vquic/quiche.h | 116 | ||||
| -rw-r--r-- | libs/libcurl/src/vquic/vquic.c | 174 | ||||
| -rw-r--r-- | libs/libcurl/src/vquic/vquic.h | 72 |
8 files changed, 3999 insertions, 3991 deletions
diff --git a/libs/libcurl/src/vquic/msh3.c b/libs/libcurl/src/vquic/msh3.c index c3e58e726a..46d7699488 100644 --- a/libs/libcurl/src/vquic/msh3.c +++ b/libs/libcurl/src/vquic/msh3.c @@ -1,527 +1,527 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * 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 - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * 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_MSH3 - -#include "urldata.h" -#include "timeval.h" -#include "multiif.h" -#include "sendf.h" -#include "connect.h" -#include "h2h3.h" -#include "msh3.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -/* #define DEBUG_HTTP3 1 */ -#ifdef DEBUG_HTTP3 -#define H3BUGF(x) x -#else -#define H3BUGF(x) do { } while(0) -#endif - -#define MSH3_REQ_INIT_BUF_LEN 8192 - -static CURLcode msh3_do_it(struct Curl_easy *data, bool *done); -static int msh3_getsock(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t *socks); -static CURLcode msh3_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection); -static unsigned int msh3_conncheck(struct Curl_easy *data, - struct connectdata *conn, - unsigned int checks_to_perform); -static Curl_recv msh3_stream_recv; -static Curl_send msh3_stream_send; -static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, - void *IfContext, - const MSH3_HEADER *Header); -static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, - void *IfContext, uint32_t Length, - const uint8_t *Data); -static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, - bool Aborted, uint64_t AbortError); -static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext); - -static const struct Curl_handler msh3_curl_handler_http3 = { - "HTTPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - msh3_do_it, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - msh3_getsock, /* proto_getsock */ - msh3_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - msh3_getsock, /* perform_getsock */ - msh3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - msh3_conncheck, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTPS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_SSL | PROTOPT_STREAM /* flags */ -}; - -static const MSH3_REQUEST_IF msh3_request_if = { - msh3_header_received, - msh3_data_received, - msh3_complete, - msh3_shutdown -}; - -void Curl_quic_ver(char *p, size_t len) -{ - uint32_t v[4]; - MsH3Version(v); - (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]); -} - -CURLcode Curl_quic_connect(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t sockfd, - int sockindex, - const struct sockaddr *addr, - socklen_t addrlen) -{ - struct quicsocket *qs = &conn->hequic[sockindex]; - bool insecure = !conn->ssl_config.verifypeer; - memset(qs, 0, sizeof(*qs)); - - (void)sockfd; - (void)addr; /* TODO - Pass address along */ - (void)addrlen; - - H3BUGF(infof(data, "creating new api/connection")); - - qs->api = MsH3ApiOpen(); - if(!qs->api) { - failf(data, "can't create msh3 api"); - return CURLE_FAILED_INIT; - } - - qs->conn = MsH3ConnectionOpen(qs->api, - conn->host.name, - (uint16_t)conn->remote_port, - insecure); - if(!qs->conn) { - failf(data, "can't create msh3 connection"); - if(qs->api) { - MsH3ApiClose(qs->api); - } - return CURLE_FAILED_INIT; - } - - return CURLE_OK; -} - -CURLcode Curl_quic_is_connected(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool *connected) -{ - struct quicsocket *qs = &conn->hequic[sockindex]; - MSH3_CONNECTION_STATE state; - - state = MsH3ConnectionGetState(qs->conn, false); - if(state == MSH3_CONN_HANDSHAKE_FAILED || state == MSH3_CONN_DISCONNECTED) { - failf(data, "failed to connect, state=%u", (uint32_t)state); - return CURLE_COULDNT_CONNECT; - } - - if(state == MSH3_CONN_CONNECTED) { - H3BUGF(infof(data, "connection connected")); - *connected = true; - conn->quic = qs; - conn->recv[sockindex] = msh3_stream_recv; - conn->send[sockindex] = msh3_stream_send; - conn->handler = &msh3_curl_handler_http3; - conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->httpversion = 30; - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - /* TODO - Clean up other happy-eyeballs connection(s)? */ - } - - return CURLE_OK; -} - -static int msh3_getsock(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t *socks) -{ - struct HTTP *stream = data->req.p.http; - int bitmap = GETSOCK_BLANK; - - socks[0] = conn->sock[FIRSTSOCKET]; - - if(stream->recv_error) { - bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); - data->state.drain++; - } - else if(stream->recv_header_len || stream->recv_data_len) { - bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); - data->state.drain++; - } - - H3BUGF(infof(data, "msh3_getsock %u", (uint32_t)data->state.drain)); - - return bitmap; -} - -static CURLcode msh3_do_it(struct Curl_easy *data, bool *done) -{ - struct HTTP *stream = data->req.p.http; - H3BUGF(infof(data, "msh3_do_it")); - stream->recv_buf = malloc(MSH3_REQ_INIT_BUF_LEN); - if(!stream->recv_buf) { - return CURLE_OUT_OF_MEMORY; - } - stream->req = ZERO_NULL; - msh3_lock_initialize(&stream->recv_lock); - stream->recv_buf_alloc = MSH3_REQ_INIT_BUF_LEN; - stream->recv_header_len = 0; - stream->recv_header_complete = false; - stream->recv_data_len = 0; - stream->recv_data_complete = false; - stream->recv_error = CURLE_OK; - return Curl_http(data, done); -} - -static unsigned int msh3_conncheck(struct Curl_easy *data, - struct connectdata *conn, - unsigned int checks_to_perform) -{ - (void)data; - (void)conn; - (void)checks_to_perform; - H3BUGF(infof(data, "msh3_conncheck")); - return CONNRESULT_NONE; -} - -static void disconnect(struct quicsocket *qs) -{ - if(qs->conn) { - MsH3ConnectionClose(qs->conn); - qs->conn = ZERO_NULL; - } - if(qs->api) { - MsH3ApiClose(qs->api); - qs->api = ZERO_NULL; - } -} - -static CURLcode msh3_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead_connection) -{ - (void)data; - (void)dead_connection; - H3BUGF(infof(data, "disconnecting (msh3)")); - disconnect(conn->quic); - return CURLE_OK; -} - -void Curl_quic_disconnect(struct Curl_easy *data, struct connectdata *conn, - int tempindex) -{ - (void)data; - if(conn->transport == TRNSPRT_QUIC) { - H3BUGF(infof(data, "disconnecting QUIC index %u", tempindex)); - disconnect(&conn->hequic[tempindex]); - } -} - -/* Requires stream->recv_lock to be held */ -static bool msh3request_ensure_room(struct HTTP *stream, size_t len) -{ - uint8_t *new_recv_buf; - const size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len; - if(cur_recv_len + len > stream->recv_buf_alloc) { - size_t new_recv_buf_alloc_len = stream->recv_buf_alloc; - do { - new_recv_buf_alloc_len <<= 1; /* TODO - handle overflow */ - } while(cur_recv_len + len > new_recv_buf_alloc_len); - new_recv_buf = malloc(new_recv_buf_alloc_len); - if(!new_recv_buf) { - return false; - } - if(cur_recv_len) { - memcpy(new_recv_buf, stream->recv_buf, cur_recv_len); - } - stream->recv_buf_alloc = new_recv_buf_alloc_len; - free(stream->recv_buf); - stream->recv_buf = new_recv_buf; - } - return true; -} - -static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, - void *IfContext, - const MSH3_HEADER *Header) -{ - struct HTTP *stream = IfContext; - size_t total_len; - (void)Request; - - if(stream->recv_header_complete) { - H3BUGF(printf("* ignoring header after data\n")); - return; - } - - msh3_lock_acquire(&stream->recv_lock); - - if((Header->NameLength == 7) && - !strncmp(H2H3_PSEUDO_STATUS, (char *)Header->Name, 7)) { - total_len = 9 + Header->ValueLength; - if(!msh3request_ensure_room(stream, total_len)) { - /* TODO - handle error */ - goto release_lock; - } - msnprintf((char *)stream->recv_buf + stream->recv_header_len, - stream->recv_buf_alloc - stream->recv_header_len, - "HTTP/3 %.*s\n", (int)Header->ValueLength, Header->Value); - } - else { - total_len = Header->NameLength + 4 + Header->ValueLength; - if(!msh3request_ensure_room(stream, total_len)) { - /* TODO - handle error */ - goto release_lock; - } - msnprintf((char *)stream->recv_buf + stream->recv_header_len, - stream->recv_buf_alloc - stream->recv_header_len, - "%.*s: %.*s\n", - (int)Header->NameLength, Header->Name, - (int)Header->ValueLength, Header->Value); - } - - stream->recv_header_len += total_len - 1; /* don't include null-terminator */ - -release_lock: - msh3_lock_release(&stream->recv_lock); -} - -static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, - void *IfContext, uint32_t Length, - const uint8_t *Data) -{ - struct HTTP *stream = IfContext; - size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len; - (void)Request; - H3BUGF(printf("* msh3_data_received %u. %zu buffered, %zu allocated\n", - Length, cur_recv_len, stream->recv_buf_alloc)); - msh3_lock_acquire(&stream->recv_lock); - if(!stream->recv_header_complete) { - H3BUGF(printf("* Headers complete!\n")); - if(!msh3request_ensure_room(stream, 2)) { - /* TODO - handle error */ - goto release_lock; - } - stream->recv_buf[stream->recv_header_len++] = '\r'; - stream->recv_buf[stream->recv_header_len++] = '\n'; - stream->recv_header_complete = true; - cur_recv_len += 2; - } - if(!msh3request_ensure_room(stream, Length)) { - /* TODO - handle error */ - goto release_lock; - } - memcpy(stream->recv_buf + cur_recv_len, Data, Length); - stream->recv_data_len += (size_t)Length; -release_lock: - msh3_lock_release(&stream->recv_lock); -} - -static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, - bool Aborted, uint64_t AbortError) -{ - struct HTTP *stream = IfContext; - (void)Request; - (void)AbortError; - H3BUGF(printf("* msh3_complete, aborted=%s\n", Aborted ? "true" : "false")); - msh3_lock_acquire(&stream->recv_lock); - if(Aborted) { - stream->recv_error = CURLE_HTTP3; /* TODO - how do we pass AbortError? */ - } - stream->recv_header_complete = true; - stream->recv_data_complete = true; - msh3_lock_release(&stream->recv_lock); -} - -static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext) -{ - struct HTTP *stream = IfContext; - (void)Request; - (void)stream; -} - -static ssize_t msh3_stream_send(struct Curl_easy *data, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct HTTP *stream = data->req.p.http; - struct quicsocket *qs = conn->quic; - struct h2h3req *hreq; - - (void)sockindex; - /* Sizes must match for cast below to work" */ - DEBUGASSERT(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo)); - - H3BUGF(infof(data, "msh3_stream_send %zu", len)); - - if(!stream->req) { - *curlcode = Curl_pseudo_headers(data, mem, len, &hreq); - if(*curlcode) { - failf(data, "Curl_pseudo_headers failed"); - return -1; - } - H3BUGF(infof(data, "starting request with %zu headers", hreq->entries)); - stream->req = MsH3RequestOpen(qs->conn, &msh3_request_if, stream, - (MSH3_HEADER*)hreq->header, hreq->entries); - Curl_pseudo_free(hreq); - if(!stream->req) { - failf(data, "request open failed"); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - *curlcode = CURLE_OK; - return len; - } - H3BUGF(infof(data, "send %zd body bytes on request %p", len, - (void *)stream->req)); - *curlcode = CURLE_SEND_ERROR; - return -1; -} - -static ssize_t msh3_stream_recv(struct Curl_easy *data, - int sockindex, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - struct HTTP *stream = data->req.p.http; - size_t outsize = 0; - (void)sockindex; - H3BUGF(infof(data, "msh3_stream_recv %zu", buffersize)); - - if(stream->recv_error) { - failf(data, "request aborted"); - *curlcode = stream->recv_error; - return -1; - } - - msh3_lock_acquire(&stream->recv_lock); - - if(stream->recv_header_len) { - outsize = buffersize; - if(stream->recv_header_len < outsize) { - outsize = stream->recv_header_len; - } - memcpy(buf, stream->recv_buf, outsize); - if(outsize < stream->recv_header_len + stream->recv_data_len) { - memmove(stream->recv_buf, stream->recv_buf + outsize, - stream->recv_header_len + stream->recv_data_len - outsize); - } - stream->recv_header_len -= outsize; - H3BUGF(infof(data, "returned %zu bytes of headers", outsize)); - } - else if(stream->recv_data_len) { - outsize = buffersize; - if(stream->recv_data_len < outsize) { - outsize = stream->recv_data_len; - } - memcpy(buf, stream->recv_buf, outsize); - if(outsize < stream->recv_data_len) { - memmove(stream->recv_buf, stream->recv_buf + outsize, - stream->recv_data_len - outsize); - } - stream->recv_data_len -= outsize; - H3BUGF(infof(data, "returned %zu bytes of data", outsize)); - } - else if(stream->recv_data_complete) { - H3BUGF(infof(data, "receive complete")); - } - - msh3_lock_release(&stream->recv_lock); - - return (ssize_t)outsize; -} - -CURLcode Curl_quic_done_sending(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - H3BUGF(infof(data, "Curl_quic_done_sending")); - if(conn->handler == &msh3_curl_handler_http3) { - struct HTTP *stream = data->req.p.http; - stream->upload_done = TRUE; - } - - return CURLE_OK; -} - -void Curl_quic_done(struct Curl_easy *data, bool premature) -{ - struct HTTP *stream = data->req.p.http; - (void)premature; - H3BUGF(infof(data, "Curl_quic_done")); - if(stream) { - if(stream->recv_buf) { - Curl_safefree(stream->recv_buf); - msh3_lock_uninitialize(&stream->recv_lock); - } - if(stream->req) { - MsH3RequestClose(stream->req); - stream->req = ZERO_NULL; - } - } -} - -bool Curl_quic_data_pending(const struct Curl_easy *data) -{ - struct HTTP *stream = data->req.p.http; - H3BUGF(infof((struct Curl_easy *)data, "Curl_quic_data_pending")); - 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 */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * 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
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * 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_MSH3
+
+#include "urldata.h"
+#include "timeval.h"
+#include "multiif.h"
+#include "sendf.h"
+#include "connect.h"
+#include "h2h3.h"
+#include "msh3.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+/* #define DEBUG_HTTP3 1 */
+#ifdef DEBUG_HTTP3
+#define H3BUGF(x) x
+#else
+#define H3BUGF(x) do { } while(0)
+#endif
+
+#define MSH3_REQ_INIT_BUF_LEN 8192
+
+static CURLcode msh3_do_it(struct Curl_easy *data, bool *done);
+static int msh3_getsock(struct Curl_easy *data,
+ struct connectdata *conn, curl_socket_t *socks);
+static CURLcode msh3_disconnect(struct Curl_easy *data,
+ struct connectdata *conn,
+ bool dead_connection);
+static unsigned int msh3_conncheck(struct Curl_easy *data,
+ struct connectdata *conn,
+ unsigned int checks_to_perform);
+static Curl_recv msh3_stream_recv;
+static Curl_send msh3_stream_send;
+static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
+ void *IfContext,
+ const MSH3_HEADER *Header);
+static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
+ void *IfContext, uint32_t Length,
+ const uint8_t *Data);
+static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
+ bool Aborted, uint64_t AbortError);
+static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext);
+
+static const struct Curl_handler msh3_curl_handler_http3 = {
+ "HTTPS", /* scheme */
+ ZERO_NULL, /* setup_connection */
+ msh3_do_it, /* do_it */
+ Curl_http_done, /* done */
+ ZERO_NULL, /* do_more */
+ ZERO_NULL, /* connect_it */
+ ZERO_NULL, /* connecting */
+ ZERO_NULL, /* doing */
+ msh3_getsock, /* proto_getsock */
+ msh3_getsock, /* doing_getsock */
+ ZERO_NULL, /* domore_getsock */
+ msh3_getsock, /* perform_getsock */
+ msh3_disconnect, /* disconnect */
+ ZERO_NULL, /* readwrite */
+ msh3_conncheck, /* connection_check */
+ ZERO_NULL, /* attach connection */
+ PORT_HTTP, /* defport */
+ CURLPROTO_HTTPS, /* protocol */
+ CURLPROTO_HTTP, /* family */
+ PROTOPT_SSL | PROTOPT_STREAM /* flags */
+};
+
+static const MSH3_REQUEST_IF msh3_request_if = {
+ msh3_header_received,
+ msh3_data_received,
+ msh3_complete,
+ msh3_shutdown
+};
+
+void Curl_quic_ver(char *p, size_t len)
+{
+ uint32_t v[4];
+ MsH3Version(v);
+ (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]);
+}
+
+CURLcode Curl_quic_connect(struct Curl_easy *data,
+ struct connectdata *conn,
+ curl_socket_t sockfd,
+ int sockindex,
+ const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ struct quicsocket *qs = &conn->hequic[sockindex];
+ bool insecure = !conn->ssl_config.verifypeer;
+ memset(qs, 0, sizeof(*qs));
+
+ (void)sockfd;
+ (void)addr; /* TODO - Pass address along */
+ (void)addrlen;
+
+ H3BUGF(infof(data, "creating new api/connection"));
+
+ qs->api = MsH3ApiOpen();
+ if(!qs->api) {
+ failf(data, "can't create msh3 api");
+ return CURLE_FAILED_INIT;
+ }
+
+ qs->conn = MsH3ConnectionOpen(qs->api,
+ conn->host.name,
+ (uint16_t)conn->remote_port,
+ insecure);
+ if(!qs->conn) {
+ failf(data, "can't create msh3 connection");
+ if(qs->api) {
+ MsH3ApiClose(qs->api);
+ }
+ return CURLE_FAILED_INIT;
+ }
+
+ return CURLE_OK;
+}
+
+CURLcode Curl_quic_is_connected(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex,
+ bool *connected)
+{
+ struct quicsocket *qs = &conn->hequic[sockindex];
+ MSH3_CONNECTION_STATE state;
+
+ state = MsH3ConnectionGetState(qs->conn, false);
+ if(state == MSH3_CONN_HANDSHAKE_FAILED || state == MSH3_CONN_DISCONNECTED) {
+ failf(data, "failed to connect, state=%u", (uint32_t)state);
+ return CURLE_COULDNT_CONNECT;
+ }
+
+ if(state == MSH3_CONN_CONNECTED) {
+ H3BUGF(infof(data, "connection connected"));
+ *connected = true;
+ conn->quic = qs;
+ conn->recv[sockindex] = msh3_stream_recv;
+ conn->send[sockindex] = msh3_stream_send;
+ conn->handler = &msh3_curl_handler_http3;
+ conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+ conn->httpversion = 30;
+ conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+ /* TODO - Clean up other happy-eyeballs connection(s)? */
+ }
+
+ return CURLE_OK;
+}
+
+static int msh3_getsock(struct Curl_easy *data,
+ struct connectdata *conn, curl_socket_t *socks)
+{
+ struct HTTP *stream = data->req.p.http;
+ int bitmap = GETSOCK_BLANK;
+
+ socks[0] = conn->sock[FIRSTSOCKET];
+
+ if(stream->recv_error) {
+ bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
+ data->state.drain++;
+ }
+ else if(stream->recv_header_len || stream->recv_data_len) {
+ bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
+ data->state.drain++;
+ }
+
+ H3BUGF(infof(data, "msh3_getsock %u", (uint32_t)data->state.drain));
+
+ return bitmap;
+}
+
+static CURLcode msh3_do_it(struct Curl_easy *data, bool *done)
+{
+ struct HTTP *stream = data->req.p.http;
+ H3BUGF(infof(data, "msh3_do_it"));
+ stream->recv_buf = malloc(MSH3_REQ_INIT_BUF_LEN);
+ if(!stream->recv_buf) {
+ return CURLE_OUT_OF_MEMORY;
+ }
+ stream->req = ZERO_NULL;
+ msh3_lock_initialize(&stream->recv_lock);
+ stream->recv_buf_alloc = MSH3_REQ_INIT_BUF_LEN;
+ stream->recv_header_len = 0;
+ stream->recv_header_complete = false;
+ stream->recv_data_len = 0;
+ stream->recv_data_complete = false;
+ stream->recv_error = CURLE_OK;
+ return Curl_http(data, done);
+}
+
+static unsigned int msh3_conncheck(struct Curl_easy *data,
+ struct connectdata *conn,
+ unsigned int checks_to_perform)
+{
+ (void)data;
+ (void)conn;
+ (void)checks_to_perform;
+ H3BUGF(infof(data, "msh3_conncheck"));
+ return CONNRESULT_NONE;
+}
+
+static void disconnect(struct quicsocket *qs)
+{
+ if(qs->conn) {
+ MsH3ConnectionClose(qs->conn);
+ qs->conn = ZERO_NULL;
+ }
+ if(qs->api) {
+ MsH3ApiClose(qs->api);
+ qs->api = ZERO_NULL;
+ }
+}
+
+static CURLcode msh3_disconnect(struct Curl_easy *data,
+ struct connectdata *conn, bool dead_connection)
+{
+ (void)data;
+ (void)dead_connection;
+ H3BUGF(infof(data, "disconnecting (msh3)"));
+ disconnect(conn->quic);
+ return CURLE_OK;
+}
+
+void Curl_quic_disconnect(struct Curl_easy *data, struct connectdata *conn,
+ int tempindex)
+{
+ (void)data;
+ if(conn->transport == TRNSPRT_QUIC) {
+ H3BUGF(infof(data, "disconnecting QUIC index %u", tempindex));
+ disconnect(&conn->hequic[tempindex]);
+ }
+}
+
+/* Requires stream->recv_lock to be held */
+static bool msh3request_ensure_room(struct HTTP *stream, size_t len)
+{
+ uint8_t *new_recv_buf;
+ const size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len;
+ if(cur_recv_len + len > stream->recv_buf_alloc) {
+ size_t new_recv_buf_alloc_len = stream->recv_buf_alloc;
+ do {
+ new_recv_buf_alloc_len <<= 1; /* TODO - handle overflow */
+ } while(cur_recv_len + len > new_recv_buf_alloc_len);
+ new_recv_buf = malloc(new_recv_buf_alloc_len);
+ if(!new_recv_buf) {
+ return false;
+ }
+ if(cur_recv_len) {
+ memcpy(new_recv_buf, stream->recv_buf, cur_recv_len);
+ }
+ stream->recv_buf_alloc = new_recv_buf_alloc_len;
+ free(stream->recv_buf);
+ stream->recv_buf = new_recv_buf;
+ }
+ return true;
+}
+
+static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
+ void *IfContext,
+ const MSH3_HEADER *Header)
+{
+ struct HTTP *stream = IfContext;
+ size_t total_len;
+ (void)Request;
+
+ if(stream->recv_header_complete) {
+ H3BUGF(printf("* ignoring header after data\n"));
+ return;
+ }
+
+ msh3_lock_acquire(&stream->recv_lock);
+
+ if((Header->NameLength == 7) &&
+ !strncmp(H2H3_PSEUDO_STATUS, (char *)Header->Name, 7)) {
+ total_len = 9 + Header->ValueLength;
+ if(!msh3request_ensure_room(stream, total_len)) {
+ /* TODO - handle error */
+ goto release_lock;
+ }
+ msnprintf((char *)stream->recv_buf + stream->recv_header_len,
+ stream->recv_buf_alloc - stream->recv_header_len,
+ "HTTP/3 %.*s\n", (int)Header->ValueLength, Header->Value);
+ }
+ else {
+ total_len = Header->NameLength + 4 + Header->ValueLength;
+ if(!msh3request_ensure_room(stream, total_len)) {
+ /* TODO - handle error */
+ goto release_lock;
+ }
+ msnprintf((char *)stream->recv_buf + stream->recv_header_len,
+ stream->recv_buf_alloc - stream->recv_header_len,
+ "%.*s: %.*s\n",
+ (int)Header->NameLength, Header->Name,
+ (int)Header->ValueLength, Header->Value);
+ }
+
+ stream->recv_header_len += total_len - 1; /* don't include null-terminator */
+
+release_lock:
+ msh3_lock_release(&stream->recv_lock);
+}
+
+static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
+ void *IfContext, uint32_t Length,
+ const uint8_t *Data)
+{
+ struct HTTP *stream = IfContext;
+ size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len;
+ (void)Request;
+ H3BUGF(printf("* msh3_data_received %u. %zu buffered, %zu allocated\n",
+ Length, cur_recv_len, stream->recv_buf_alloc));
+ msh3_lock_acquire(&stream->recv_lock);
+ if(!stream->recv_header_complete) {
+ H3BUGF(printf("* Headers complete!\n"));
+ if(!msh3request_ensure_room(stream, 2)) {
+ /* TODO - handle error */
+ goto release_lock;
+ }
+ stream->recv_buf[stream->recv_header_len++] = '\r';
+ stream->recv_buf[stream->recv_header_len++] = '\n';
+ stream->recv_header_complete = true;
+ cur_recv_len += 2;
+ }
+ if(!msh3request_ensure_room(stream, Length)) {
+ /* TODO - handle error */
+ goto release_lock;
+ }
+ memcpy(stream->recv_buf + cur_recv_len, Data, Length);
+ stream->recv_data_len += (size_t)Length;
+release_lock:
+ msh3_lock_release(&stream->recv_lock);
+}
+
+static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
+ bool Aborted, uint64_t AbortError)
+{
+ struct HTTP *stream = IfContext;
+ (void)Request;
+ (void)AbortError;
+ H3BUGF(printf("* msh3_complete, aborted=%s\n", Aborted ? "true" : "false"));
+ msh3_lock_acquire(&stream->recv_lock);
+ if(Aborted) {
+ stream->recv_error = CURLE_HTTP3; /* TODO - how do we pass AbortError? */
+ }
+ stream->recv_header_complete = true;
+ stream->recv_data_complete = true;
+ msh3_lock_release(&stream->recv_lock);
+}
+
+static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext)
+{
+ struct HTTP *stream = IfContext;
+ (void)Request;
+ (void)stream;
+}
+
+static ssize_t msh3_stream_send(struct Curl_easy *data,
+ int sockindex,
+ const void *mem,
+ size_t len,
+ CURLcode *curlcode)
+{
+ struct connectdata *conn = data->conn;
+ struct HTTP *stream = data->req.p.http;
+ struct quicsocket *qs = conn->quic;
+ struct h2h3req *hreq;
+
+ (void)sockindex;
+ /* Sizes must match for cast below to work" */
+ DEBUGASSERT(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo));
+
+ H3BUGF(infof(data, "msh3_stream_send %zu", len));
+
+ if(!stream->req) {
+ *curlcode = Curl_pseudo_headers(data, mem, len, &hreq);
+ if(*curlcode) {
+ failf(data, "Curl_pseudo_headers failed");
+ return -1;
+ }
+ H3BUGF(infof(data, "starting request with %zu headers", hreq->entries));
+ stream->req = MsH3RequestOpen(qs->conn, &msh3_request_if, stream,
+ (MSH3_HEADER*)hreq->header, hreq->entries);
+ Curl_pseudo_free(hreq);
+ if(!stream->req) {
+ failf(data, "request open failed");
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+ }
+ *curlcode = CURLE_OK;
+ return len;
+ }
+ H3BUGF(infof(data, "send %zd body bytes on request %p", len,
+ (void *)stream->req));
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+}
+
+static ssize_t msh3_stream_recv(struct Curl_easy *data,
+ int sockindex,
+ char *buf,
+ size_t buffersize,
+ CURLcode *curlcode)
+{
+ struct HTTP *stream = data->req.p.http;
+ size_t outsize = 0;
+ (void)sockindex;
+ H3BUGF(infof(data, "msh3_stream_recv %zu", buffersize));
+
+ if(stream->recv_error) {
+ failf(data, "request aborted");
+ *curlcode = stream->recv_error;
+ return -1;
+ }
+
+ msh3_lock_acquire(&stream->recv_lock);
+
+ if(stream->recv_header_len) {
+ outsize = buffersize;
+ if(stream->recv_header_len < outsize) {
+ outsize = stream->recv_header_len;
+ }
+ memcpy(buf, stream->recv_buf, outsize);
+ if(outsize < stream->recv_header_len + stream->recv_data_len) {
+ memmove(stream->recv_buf, stream->recv_buf + outsize,
+ stream->recv_header_len + stream->recv_data_len - outsize);
+ }
+ stream->recv_header_len -= outsize;
+ H3BUGF(infof(data, "returned %zu bytes of headers", outsize));
+ }
+ else if(stream->recv_data_len) {
+ outsize = buffersize;
+ if(stream->recv_data_len < outsize) {
+ outsize = stream->recv_data_len;
+ }
+ memcpy(buf, stream->recv_buf, outsize);
+ if(outsize < stream->recv_data_len) {
+ memmove(stream->recv_buf, stream->recv_buf + outsize,
+ stream->recv_data_len - outsize);
+ }
+ stream->recv_data_len -= outsize;
+ H3BUGF(infof(data, "returned %zu bytes of data", outsize));
+ }
+ else if(stream->recv_data_complete) {
+ H3BUGF(infof(data, "receive complete"));
+ }
+
+ msh3_lock_release(&stream->recv_lock);
+
+ return (ssize_t)outsize;
+}
+
+CURLcode Curl_quic_done_sending(struct Curl_easy *data)
+{
+ struct connectdata *conn = data->conn;
+ H3BUGF(infof(data, "Curl_quic_done_sending"));
+ if(conn->handler == &msh3_curl_handler_http3) {
+ struct HTTP *stream = data->req.p.http;
+ stream->upload_done = TRUE;
+ }
+
+ return CURLE_OK;
+}
+
+void Curl_quic_done(struct Curl_easy *data, bool premature)
+{
+ struct HTTP *stream = data->req.p.http;
+ (void)premature;
+ H3BUGF(infof(data, "Curl_quic_done"));
+ if(stream) {
+ if(stream->recv_buf) {
+ Curl_safefree(stream->recv_buf);
+ msh3_lock_uninitialize(&stream->recv_lock);
+ }
+ if(stream->req) {
+ MsH3RequestClose(stream->req);
+ stream->req = ZERO_NULL;
+ }
+ }
+}
+
+bool Curl_quic_data_pending(const struct Curl_easy *data)
+{
+ struct HTTP *stream = data->req.p.http;
+ H3BUGF(infof((struct Curl_easy *)data, "Curl_quic_data_pending"));
+ 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 ce884d92dd..06c2d79d31 100644 --- a/libs/libcurl/src/vquic/msh3.h +++ b/libs/libcurl/src/vquic/msh3.h @@ -1,40 +1,40 @@ -#ifndef HEADER_CURL_VQUIC_MSH3_H -#define HEADER_CURL_VQUIC_MSH3_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * 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 - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * 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_MSH3 - -#include <msh3.h> - -struct quicsocket { - MSH3_API* api; - MSH3_CONNECTION* conn; -}; - -#endif /* USE_MSQUIC */ - -#endif /* HEADER_CURL_VQUIC_MSH3_H */ +#ifndef HEADER_CURL_VQUIC_MSH3_H
+#define HEADER_CURL_VQUIC_MSH3_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * 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
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * 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_MSH3
+
+#include <msh3.h>
+
+struct quicsocket {
+ MSH3_API* api;
+ MSH3_CONNECTION* conn;
+};
+
+#endif /* USE_MSQUIC */
+
+#endif /* HEADER_CURL_VQUIC_MSH3_H */
diff --git a/libs/libcurl/src/vquic/ngtcp2.c b/libs/libcurl/src/vquic/ngtcp2.c index 097cca44b0..fadcf70be0 100644 --- a/libs/libcurl/src/vquic/ngtcp2.c +++ b/libs/libcurl/src/vquic/ngtcp2.c @@ -1,2254 +1,2266 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * 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 - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * 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 <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> -#include "vtls/gtls.h" -#elif defined(USE_WOLFSSL) -#include <ngtcp2/ngtcp2_crypto_wolfssl.h> -#include "vtls/wolfssl.h" -#endif -#include "urldata.h" -#include "sendf.h" -#include "strdup.h" -#include "rand.h" -#include "ngtcp2.h" -#include "multiif.h" -#include "strcase.h" -#include "connect.h" -#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" -#include "curl_memory.h" -#include "memdebug.h" - -/* #define DEBUG_NGTCP2 */ -#ifdef CURLDEBUG -#define DEBUG_HTTP3 -#endif -#ifdef DEBUG_HTTP3 -#define H3BUGF(x) x -#else -#define H3BUGF(x) do { } while(0) -#endif - -#define H3_ALPN_H3_29 "\x5h3-29" -#define H3_ALPN_H3 "\x2h3" - -/* - * This holds outgoing HTTP/3 stream data that is used by nghttp3 until acked. - * It is used as a circular buffer. Add new bytes at the end until it reaches - * the far end, then start over at index 0 again. - */ - -#define H3_SEND_SIZE (256*1024) -struct h3out { - uint8_t buf[H3_SEND_SIZE]; - size_t used; /* number of bytes used in the buffer */ - size_t windex; /* index in the buffer where to start writing the next - data block */ -}; - -#define QUIC_MAX_STREAMS (256*1024) -#define QUIC_MAX_DATA (1*1024*1024) -#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS) - -#ifdef USE_OPENSSL -#define QUIC_CIPHERS \ - "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ - "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" -#define QUIC_GROUPS "P-256:X25519:P-384:P-521" -#elif defined(USE_GNUTLS) -#define QUIC_PRIORITY \ - "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ - "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ - "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ - "%DISABLE_TLS13_COMPAT_MODE" -#elif defined(USE_WOLFSSL) -#define QUIC_CIPHERS \ - "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ - "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" -#define QUIC_GROUPS "P-256:P-384:P-521" -#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); -static CURLcode ng_flush_egress(struct Curl_easy *data, int sockfd, - struct quicsocket *qs); -static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, - uint64_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(); - return ct.tv_sec * NGTCP2_SECONDS + ct.tv_usec * NGTCP2_MICROSECONDS; -} - -#ifdef DEBUG_NGTCP2 -static void quic_printf(void *user_data, const char *fmt, ...) -{ - va_list ap; - (void)user_data; /* TODO, use this to do infof() instead long-term */ - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, "\n"); -} -#endif - -static void qlog_callback(void *user_data, uint32_t flags, - const void *data, size_t datalen) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - (void)flags; - if(qs->qlogfd != -1) { - ssize_t rc = write(qs->qlogfd, data, datalen); - if(rc == -1) { - /* on write error, stop further write attempts */ - close(qs->qlogfd); - qs->qlogfd = -1; - } - } - -} - -static void quic_settings(struct quicsocket *qs, - uint64_t stream_buffer_size) -{ - ngtcp2_settings *s = &qs->settings; - ngtcp2_transport_params *t = &qs->transport_params; - ngtcp2_settings_default(s); - ngtcp2_transport_params_default(t); -#ifdef DEBUG_NGTCP2 - s->log_printf = quic_printf; -#else - s->log_printf = NULL; -#endif - s->initial_ts = timestamp(); - t->initial_max_stream_data_bidi_local = stream_buffer_size; - t->initial_max_stream_data_bidi_remote = QUIC_MAX_STREAMS; - t->initial_max_stream_data_uni = QUIC_MAX_STREAMS; - t->initial_max_data = QUIC_MAX_DATA; - t->initial_max_streams_bidi = 1; - t->initial_max_streams_uni = 3; - t->max_idle_timeout = QUIC_IDLE_TIMEOUT; - if(qs->qlogfd != -1) { - s->qlog.write = qlog_callback; - } -} - -#ifdef USE_OPENSSL -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} -#elif defined(USE_GNUTLS) -static int keylog_callback(gnutls_session_t session, const char *label, - const gnutls_datum_t *secret) -{ - gnutls_datum_t crandom; - gnutls_datum_t srandom; - - gnutls_session_get_random(session, &crandom, &srandom); - if(crandom.size != 32) { - return -1; - } - - Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); - return 0; -} -#elif defined(USE_WOLFSSL) -#if defined(HAVE_SECRET_CALLBACK) -static void keylog_callback(const WOLFSSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} -#endif -#endif - -static int init_ngh3_conn(struct quicsocket *qs); - -#ifdef USE_OPENSSL -static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); - -#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)); - failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer); - return NULL; - } - - if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) { - failf(data, "SSL_CTX_set1_groups_list failed"); - return NULL; - } -#endif - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); - } - - if(conn->ssl_config.verifypeer) { - 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"); - } -#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; -} - -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) -{ - const uint8_t *alpn = NULL; - size_t alpnlen = 0; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = qs->conn->host.name; - - DEBUGASSERT(!qs->ssl); - qs->ssl = SSL_new(qs->sslctx); - - SSL_set_app_data(qs->ssl, &qs->conn_ref); - SSL_set_connect_state(qs->ssl); - SSL_set_quic_use_legacy_codepoint(qs->ssl, 0); - - alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; - alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; - if(alpn) - SSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen); - - /* set SNI */ - SSL_set_tlsext_host_name(qs->ssl, hostname); - return 0; -} -#elif defined(USE_GNUTLS) -static int quic_init_ssl(struct quicsocket *qs) -{ - gnutls_datum_t alpn[2]; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = qs->conn->host.name; - int rc; - - DEBUGASSERT(!qs->ssl); - - gnutls_init(&qs->ssl, GNUTLS_CLIENT); - gnutls_session_set_ptr(qs->ssl, &qs->conn_ref); - - if(ngtcp2_crypto_gnutls_configure_client_session(qs->ssl) != 0) { - H3BUGF(fprintf(stderr, - "ngtcp2_crypto_gnutls_configure_client_session failed\n")); - return 1; - } - - 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))); - return 1; - } - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - gnutls_session_set_keylog_function(qs->ssl, keylog_callback); - } - - if(qs->cred) - gnutls_certificate_free_credentials(qs->cred); - - rc = gnutls_certificate_allocate_credentials(&qs->cred); - if(rc < 0) { - H3BUGF(fprintf(stderr, - "gnutls_certificate_allocate_credentials failed: %s\n", - gnutls_strerror(rc))); - return 1; - } - - rc = gnutls_certificate_set_x509_system_trust(qs->cred); - if(rc < 0) { - H3BUGF(fprintf(stderr, - "gnutls_certificate_set_x509_system_trust failed: %s\n", - gnutls_strerror(rc))); - return 1; - } - - rc = gnutls_credentials_set(qs->ssl, GNUTLS_CRD_CERTIFICATE, qs->cred); - if(rc < 0) { - H3BUGF(fprintf(stderr, "gnutls_credentials_set failed: %s\n", - gnutls_strerror(rc))); - return 1; - } - - /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */ - alpn[0].data = (unsigned char *)H3_ALPN_H3_29 + 1; - alpn[0].size = sizeof(H3_ALPN_H3_29) - 2; - alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1; - alpn[1].size = sizeof(H3_ALPN_H3) - 2; - - gnutls_alpn_set_protocols(qs->ssl, alpn, 2, GNUTLS_ALPN_MANDATORY); - - /* set SNI */ - gnutls_server_name_set(qs->ssl, GNUTLS_NAME_DNS, hostname, strlen(hostname)); - return 0; -} -#elif defined(USE_WOLFSSL) - -static WOLFSSL_CTX *quic_ssl_ctx(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); - - if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); - return NULL; - } - - wolfSSL_CTX_set_default_verify_paths(ssl_ctx); - - if(wolfSSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) { - 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; - } - - if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) { - failf(data, "SSL_CTX_set1_groups_list failed"); - return NULL; - } - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { -#if defined(HAVE_SECRET_CALLBACK) - wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); -#else - failf(data, "wolfSSL was built without keylog callback"); - return NULL; -#endif - } - - if(conn->ssl_config.verifypeer) { - const char * const ssl_cafile = conn->ssl_config.CAfile; - const char * const ssl_capath = conn->ssl_config.CApath; - - if(ssl_cafile || ssl_capath) { - wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); - /* tell wolfSSL where to find CA certificates that are used to verify - the server's certificate. */ - if(!wolfSSL_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"); - } -#ifdef CURL_CA_FALLBACK - else { - /* verifying the peer without any CA certificates won't work so - use wolfssl's built-in default as fallback */ - wolfSSL_CTX_set_default_verify_paths(ssl_ctx); - } -#endif - } - else { - wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); - } - - return ssl_ctx; -} - -/** SSL callbacks ***/ - -static int quic_init_ssl(struct quicsocket *qs) -{ - const uint8_t *alpn = NULL; - size_t alpnlen = 0; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = qs->conn->host.name; - - DEBUGASSERT(!qs->ssl); - qs->ssl = SSL_new(qs->sslctx); - - wolfSSL_set_app_data(qs->ssl, &qs->conn_ref); - wolfSSL_set_connect_state(qs->ssl); - wolfSSL_set_quic_use_legacy_codepoint(qs->ssl, 0); - - alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; - alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; - if(alpn) - wolfSSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen); - - /* set SNI */ - wolfSSL_UseSNI(qs->ssl, WOLFSSL_SNI_HOST_NAME, - hostname, (unsigned short)strlen(hostname)); - - return 0; -} -#endif /* defined(USE_WOLFSSL) */ - -static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data) -{ - (void)user_data; - (void)tconn; - 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, uint32_t flags, - int64_t stream_id, uint64_t offset, - const uint8_t *buf, size_t buflen, - void *user_data, void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - nghttp3_ssize nconsumed; - int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; - (void)offset; - (void)stream_user_data; - - 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; - } - - /* 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); - - return 0; -} - -static int -cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id, - uint64_t offset, uint64_t datalen, void *user_data, - void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - int rv; - (void)stream_id; - (void)tconn; - (void)offset; - (void)datalen; - (void)stream_user_data; - - rv = nghttp3_conn_add_ack_offset(qs->h3conn, stream_id, datalen); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, - int64_t stream_id, uint64_t app_error_code, - void *user_data, void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - int rv; - (void)tconn; - (void)stream_user_data; - /* stream is closed... */ - - if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { - app_error_code = NGHTTP3_H3_NO_ERROR; - } - - 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; - } - - return 0; -} - -static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, - uint64_t final_size, uint64_t app_error_code, - void *user_data, void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - int rv; - (void)tconn; - (void)final_size; - (void)app_error_code; - (void)stream_user_data; - - rv = nghttp3_conn_shutdown_stream_read(qs->h3conn, stream_id); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - int rv; - (void)tconn; - (void)app_error_code; - (void)stream_user_data; - - rv = nghttp3_conn_shutdown_stream_read(qs->h3conn, stream_id); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn, - uint64_t max_streams, - void *user_data) -{ - (void)tconn; - (void)max_streams; - (void)user_data; - - return 0; -} - -static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, - uint64_t max_data, void *user_data, - void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - int rv; - (void)tconn; - (void)max_data; - (void)stream_user_data; - - rv = nghttp3_conn_unblock_stream(qs->h3conn, stream_id); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static void cb_rand(uint8_t *dest, size_t destlen, - const ngtcp2_rand_ctx *rand_ctx) -{ - CURLcode result; - (void)rand_ctx; - - result = Curl_rand(NULL, dest, destlen); - if(result) { - /* cb_rand is only used for non-cryptographic context. If Curl_rand - failed, just fill 0 and call it *random*. */ - memset(dest, 0, destlen); - } -} - -static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid, - uint8_t *token, size_t cidlen, - void *user_data) -{ - CURLcode result; - (void)tconn; - (void)user_data; - - result = Curl_rand(NULL, cid->data, cidlen); - if(result) - return NGTCP2_ERR_CALLBACK_FAILURE; - cid->datalen = cidlen; - - result = Curl_rand(NULL, token, NGTCP2_STATELESS_RESET_TOKENLEN); - if(result) - return NGTCP2_ERR_CALLBACK_FAILURE; - - 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 */ - ngtcp2_crypto_recv_crypto_data_cb, - cb_handshake_completed, - NULL, /* recv_version_negotiation */ - ngtcp2_crypto_encrypt_cb, - ngtcp2_crypto_decrypt_cb, - ngtcp2_crypto_hp_mask_cb, - cb_recv_stream_data, - cb_acked_stream_data_offset, - NULL, /* stream_open */ - cb_stream_close, - NULL, /* recv_stateless_reset */ - ngtcp2_crypto_recv_retry_cb, - cb_extend_max_local_streams_bidi, - NULL, /* extend_max_local_streams_uni */ - cb_rand, - cb_get_new_connection_id, - NULL, /* remove_connection_id */ - ngtcp2_crypto_update_key_cb, /* update_key */ - NULL, /* path_validation */ - NULL, /* select_preferred_addr */ - cb_stream_reset, - NULL, /* extend_max_remote_streams_bidi */ - NULL, /* extend_max_remote_streams_uni */ - cb_extend_max_stream_data, - NULL, /* dcid_status */ - NULL, /* handshake_confirmed */ - NULL, /* recv_new_token */ - ngtcp2_crypto_delete_crypto_aead_ctx_cb, - ngtcp2_crypto_delete_crypto_cipher_ctx_cb, - NULL, /* recv_datagram */ - NULL, /* ack_datagram */ - NULL, /* lost_datagram */ - ngtcp2_crypto_get_path_challenge_data_cb, - cb_stream_stop_sending, - NULL, /* version_negotiation */ - cb_recv_rx_key, - NULL, /* recv_tx_key */ - NULL, /* early_data_rejected */ -}; - -/* - * Might be called twice for happy eyeballs. - */ -CURLcode Curl_quic_connect(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t sockfd, - int sockindex, - const struct sockaddr *addr, - socklen_t addrlen) -{ - int rc; - int rv; - CURLcode result; - ngtcp2_path path; /* TODO: this must be initialized properly */ - struct quicsocket *qs = &conn->hequic[sockindex]; - char ipbuf[40]; - int port; - int qfd; - - if(qs->conn) - Curl_quic_disconnect(data, conn, sockindex); - qs->conn = conn; - - /* extract the used address as a string */ - if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) { - char buffer[STRERROR_LEN]; - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - infof(data, "Connect socket %d over QUIC to %s:%d", - sockfd, ipbuf, port); - - qs->version = NGTCP2_PROTO_VER_MAX; -#ifdef USE_OPENSSL - 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; -#elif defined(USE_WOLFSSL) - qs->sslctx = quic_ssl_ctx(data); - if(!qs->sslctx) - return CURLE_QUIC_CONNECT_ERROR; -#endif - - if(quic_init_ssl(qs)) - return CURLE_QUIC_CONNECT_ERROR; - - qs->dcid.datalen = NGTCP2_MAX_CIDLEN; - result = Curl_rand(data, qs->dcid.data, NGTCP2_MAX_CIDLEN); - if(result) - return result; - - qs->scid.datalen = NGTCP2_MAX_CIDLEN; - result = Curl_rand(data, qs->scid.data, NGTCP2_MAX_CIDLEN); - if(result) - return result; - - (void)Curl_qlogdir(data, qs->scid.data, NGTCP2_MAX_CIDLEN, &qfd); - qs->qlogfd = qfd; /* -1 if failure above */ - quic_settings(qs, data->set.buffer_size); - - qs->local_addrlen = sizeof(qs->local_addr); - rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr, - &qs->local_addrlen); - if(rv == -1) - return CURLE_QUIC_CONNECT_ERROR; - - ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr, - qs->local_addrlen); - ngtcp2_addr_init(&path.remote, addr, addrlen); - - rc = ngtcp2_conn_client_new(&qs->qconn, &qs->dcid, &qs->scid, &path, - NGTCP2_PROTO_VER_V1, &ng_callbacks, - &qs->settings, &qs->transport_params, NULL, qs); - if(rc) - return CURLE_QUIC_CONNECT_ERROR; - - 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; -} - -/* - * Store ngtcp2 version info in this buffer. - */ -void Curl_quic_ver(char *p, size_t len) -{ - const ngtcp2_info *ng2 = ngtcp2_version(0); - const nghttp3_info *ht3 = nghttp3_version(0); - (void)msnprintf(p, len, "ngtcp2/%s nghttp3/%s", - ng2->version_str, ht3->version_str); -} - -static int ng_getsock(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t *socks) -{ - 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]; - - /* in a HTTP/2 connection we can basically always get a frame so we should - always be ready for one */ - 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 && - (!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; -} - -static void qs_disconnect(struct quicsocket *qs) -{ - char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; - ngtcp2_tstamp ts; - ngtcp2_ssize rc; - - if(!qs->conn) /* already closed */ - return; - ts = timestamp(); - rc = ngtcp2_conn_write_connection_close(qs->qconn, NULL, /* path */ - NULL, /* pkt_info */ - (uint8_t *)buffer, sizeof(buffer), - &qs->last_error, 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); - qs->qlogfd = -1; - } - if(qs->ssl) -#ifdef USE_OPENSSL - SSL_free(qs->ssl); -#elif defined(USE_GNUTLS) - gnutls_deinit(qs->ssl); -#elif defined(USE_WOLFSSL) - wolfSSL_free(qs->ssl); -#endif - qs->ssl = NULL; -#ifdef USE_GNUTLS - if(qs->cred) { - gnutls_certificate_free_credentials(qs->cred); - qs->cred = NULL; - } -#endif - free(qs->pktbuf); - nghttp3_conn_del(qs->h3conn); - ngtcp2_conn_del(qs->qconn); -#ifdef USE_OPENSSL - SSL_CTX_free(qs->sslctx); -#elif defined(USE_WOLFSSL) - wolfSSL_CTX_free(qs->sslctx); -#endif -} - -void Curl_quic_disconnect(struct Curl_easy *data, - struct connectdata *conn, - int tempindex) -{ - (void)data; - if(conn->transport == TRNSPRT_QUIC) - qs_disconnect(&conn->hequic[tempindex]); -} - -static CURLcode ng_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) -{ - (void)dead_connection; - Curl_quic_disconnect(data, conn, 0); - Curl_quic_disconnect(data, conn, 1); - return CURLE_OK; -} - -static unsigned int ng_conncheck(struct Curl_easy *data, - struct connectdata *conn, - unsigned int checks_to_perform) -{ - (void)data; - (void)conn; - (void)checks_to_perform; - return CONNRESULT_NONE; -} - -static const struct Curl_handler Curl_handler_http3 = { - "HTTPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ng_getsock, /* proto_getsock */ - ng_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ng_getsock, /* perform_getsock */ - ng_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - ng_conncheck, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTPS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_SSL | PROTOPT_STREAM /* flags */ -}; - -static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) -{ - struct Curl_easy *data = stream_user_data; - struct HTTP *stream = data->req.p.http; - (void)conn; - (void)stream_id; - (void)app_error_code; - (void)user_data; - H3BUGF(infof(data, "cb_h3_stream_close CALLED")); - - stream->closed = TRUE; - stream->error3 = app_error_code; - 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; -} - -/* - * 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 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); - 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 = Curl_dyn_addn(&stream->overflow, buf, ncopy); - return result; -} - -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.p.http; - CURLcode result = CURLE_OK; - (void)conn; - - result = write_data(stream, buf, buflen); - if(result) { - return -1; - } - stream->unacked_window += buflen; - (void)stream_id; - (void)user_data; - return 0; -} - -static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id, - size_t consumed, void *user_data, - void *stream_user_data) -{ - 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; -} - -/* Decode HTTP status code. Returns -1 if no valid status code was - decoded. (duplicate from http2.c) */ -static int decode_status_code(const uint8_t *value, size_t len) -{ - int i; - int res; - - if(len != 3) { - return -1; - } - - res = 0; - - for(i = 0; i < 3; ++i) { - char c = value[i]; - - if(c < '0' || c > '9') { - return -1; - } - - res *= 10; - res += c - '0'; - } - - return res; -} - -static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, - int fin, void *user_data, void *stream_user_data) -{ - struct Curl_easy *data = stream_user_data; - struct HTTP *stream = data->req.p.http; - CURLcode result = CURLE_OK; - (void)conn; - (void)stream_id; - (void)user_data; - (void)fin; - - /* add a CRLF only if we've received some headers */ - if(stream->firstheader) { - result = write_data(stream, "\r\n", 2); - if(result) { - return -1; - } - } - - if(stream->status_code / 100 != 1) { - stream->bodystarted = TRUE; - } - return 0; -} - -static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, - int32_t token, nghttp3_rcbuf *name, - nghttp3_rcbuf *value, uint8_t flags, - void *user_data, void *stream_user_data) -{ - nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); - nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); - struct Curl_easy *data = stream_user_data; - struct HTTP *stream = data->req.p.http; - CURLcode result = CURLE_OK; - (void)conn; - (void)stream_id; - (void)token; - (void)flags; - (void)user_data; - - if(token == NGHTTP3_QPACK_TOKEN__STATUS) { - char line[14]; /* status line is always 13 characters long */ - size_t ncopy; - stream->status_code = decode_status_code(h3val.base, h3val.len); - DEBUGASSERT(stream->status_code != -1); - ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", - stream->status_code); - result = write_data(stream, line, ncopy); - if(result) { - return -1; - } - } - else { - /* store as a HTTP1-style header */ - result = write_data(stream, h3name.base, h3name.len); - if(result) { - return -1; - } - result = write_data(stream, ": ", 2); - if(result) { - return -1; - } - result = write_data(stream, h3val.base, h3val.len); - if(result) { - return -1; - } - result = write_data(stream, "\r\n", 2); - if(result) { - return -1; - } - } - - stream->firstheader = TRUE; - return 0; -} - -static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) -{ - struct quicsocket *qs = user_data; - int rv; - (void)conn; - (void)stream_user_data; - - rv = ngtcp2_conn_shutdown_stream_read(qs->qconn, stream_id, app_error_code); - if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) { - struct quicsocket *qs = user_data; - int rv; - (void)conn; - (void)stream_user_data; - - rv = ngtcp2_conn_shutdown_stream_write(qs->qconn, stream_id, app_error_code); - if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static nghttp3_callbacks ngh3_callbacks = { - cb_h3_acked_stream_data, /* acked_stream_data */ - cb_h3_stream_close, - cb_h3_recv_data, - cb_h3_deferred_consume, - NULL, /* begin_headers */ - cb_h3_recv_header, - cb_h3_end_headers, - NULL, /* begin_trailers */ - cb_h3_recv_header, - NULL, /* end_trailers */ - cb_h3_stop_sending, - NULL, /* end_stream */ - cb_h3_reset_stream, - NULL /* shutdown */ -}; - -static int init_ngh3_conn(struct quicsocket *qs) -{ - CURLcode result; - int rc; - int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id; - - if(ngtcp2_conn_get_max_local_streams_uni(qs->qconn) < 3) { - return CURLE_QUIC_CONNECT_ERROR; - } - - nghttp3_settings_default(&qs->h3settings); - - rc = nghttp3_conn_client_new(&qs->h3conn, - &ngh3_callbacks, - &qs->h3settings, - nghttp3_mem_default(), - qs); - if(rc) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - - rc = ngtcp2_conn_open_uni_stream(qs->qconn, &ctrl_stream_id, NULL); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; - } - - rc = nghttp3_conn_bind_control_stream(qs->h3conn, ctrl_stream_id); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; - } - - rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_enc_stream_id, NULL); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; - } - - rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_dec_stream_id, NULL); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; - } - - rc = nghttp3_conn_bind_qpack_streams(qs->h3conn, qpack_enc_stream_id, - qpack_dec_stream_id); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; - } - - return CURLE_OK; - fail: - - return result; -} - -static Curl_recv ngh3_stream_recv; -static Curl_send ngh3_stream_send; - -static size_t drain_overflow_buffer(struct HTTP *stream) -{ - size_t overlen = Curl_dyn_len(&stream->overflow); - size_t ncopy = CURLMIN(overlen, stream->len); - if(ncopy > 0) { - memcpy(stream->mem, Curl_dyn_ptr(&stream->overflow), ncopy); - stream->len -= ncopy; - stream->mem += ncopy; - stream->memlen += ncopy; - 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; -} - -/* incoming data frames on the h3 stream */ -static ssize_t ngh3_stream_recv(struct Curl_easy *data, - int sockindex, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[sockindex]; - struct HTTP *stream = data->req.p.http; - struct quicsocket *qs = conn->quic; - - if(!stream->memlen) { - /* remember where to store incoming data for this stream and how big the - buffer is */ - stream->mem = buf; - stream->len = buffersize; - } - /* 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(data, sockfd, qs)) { - *curlcode = CURLE_RECV_ERROR; - return -1; - } - if(ng_flush_egress(data, sockfd, qs)) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - - if(stream->memlen) { - ssize_t memlen = stream->memlen; - /* data arrived */ - *curlcode = CURLE_OK; - /* reset to allow more data to come */ - stream->memlen = 0; - stream->mem = buf; - stream->len = buffersize; - /* 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(data, sockfd, qs)) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - return memlen; - } - - if(stream->closed) { - if(stream->error3 != NGHTTP3_H3_NO_ERROR) { - failf(data, - "HTTP/3 stream %" PRId64 " was not closed cleanly: (err %" PRIu64 - ")", - stream->stream3_id, stream->error3); - *curlcode = CURLE_HTTP3; - return -1; - } - - if(!stream->bodystarted) { - failf(data, - "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" - " all response header fields, treated as error", - stream->stream3_id); - *curlcode = CURLE_HTTP3; - return -1; - } - - *curlcode = CURLE_OK; - return 0; - } - - infof(data, "ngh3_stream_recv returns 0 bytes and EAGAIN"); - *curlcode = CURLE_AGAIN; - return -1; -} - -/* this amount of data has now been acked on this stream */ -static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, - uint64_t datalen, void *user_data, - void *stream_user_data) -{ - struct Curl_easy *data = stream_user_data; - struct HTTP *stream = data->req.p.http; - (void)user_data; - - if(!data->set.postfields) { - stream->h3out->used -= datalen; - H3BUGF(infof(data, - "cb_h3_acked_stream_data, %zd bytes, %zd left unacked", - datalen, stream->h3out->used)); - DEBUGASSERT(stream->h3out->used < H3_SEND_SIZE); - - if(stream->h3out->used == 0) { - int rv = nghttp3_conn_resume_stream(conn, stream_id); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - } - } - return 0; -} - -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; - struct HTTP *stream = data->req.p.http; - (void)conn; - (void)stream_id; - (void)user_data; - (void)veccnt; - - if(data->set.postfields) { - vec[0].base = data->set.postfields; - vec[0].len = data->state.infilesize; - *pflags = NGHTTP3_DATA_FLAG_EOF; - 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 - delete it. Append the data at the end of the h3out buffer. Since we can - only return consecutive data, copy the amount that fits and the next - part comes in next invoke. */ - struct h3out *out = stream->h3out; - if(nread + out->windex > H3_SEND_SIZE) - nread = H3_SEND_SIZE - out->windex; - - memcpy(&out->buf[out->windex], stream->upload_mem, nread); - - /* that's the chunk we return to nghttp3 */ - vec[0].base = &out->buf[out->windex]; - vec[0].len = nread; - - out->windex += nread; - out->used += nread; - - if(out->windex == H3_SEND_SIZE) - out->windex = 0; /* wrap */ - stream->upload_mem += nread; - stream->upload_len -= nread; - if(data->state.infilesize != -1) { - stream->upload_left -= nread; - if(!stream->upload_left) - *pflags = NGHTTP3_DATA_FLAG_EOF; - } - H3BUGF(infof(data, "cb_h3_readfunction %zd bytes%s (at %zd unacked)", - nread, *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"", - out->used)); - } - if(stream->upload_done && !stream->upload_len && - (stream->upload_left <= 0)) { - H3BUGF(infof(data, "cb_h3_readfunction sets EOF")); - *pflags = NGHTTP3_DATA_FLAG_EOF; - return nread ? 1 : 0; - } - else if(!nread) { - return NGHTTP3_ERR_WOULDBLOCK; - } - return 1; -} - -/* Index where :authority header field will appear in request header - field list. */ -#define AUTHORITY_DST_IDX 3 - -static CURLcode http_request(struct Curl_easy *data, const void *mem, - size_t len) -{ - struct connectdata *conn = data->conn; - struct HTTP *stream = data->req.p.http; - size_t nheader; - 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) { - failf(data, "can get bidi streams"); - result = CURLE_SEND_ERROR; - goto fail; - } - - stream->stream3_id = stream3_id; - stream->h3req = TRUE; /* senf off! */ - Curl_dyn_init(&stream->overflow, CURL_MAX_READ_SIZE); - - result = Curl_pseudo_headers(data, mem, len, &hreq); - if(result) - goto fail; - nheader = hreq->entries; - - nva = malloc(sizeof(nghttp3_nv) * nheader); - if(!nva) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - 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; - nva[i].flags = NGHTTP3_NV_FLAG_NONE; - } - } - - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - case HTTPREQ_PUT: { - nghttp3_data_reader data_reader; - if(data->state.infilesize != -1) - stream->upload_left = data->state.infilesize; - else - /* data sending without specifying the data amount up front */ - stream->upload_left = -1; /* unknown, but not zero */ - - data_reader.read_data = cb_h3_readfunction; - - h3out = calloc(sizeof(struct h3out), 1); - if(!h3out) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - stream->h3out = h3out; - - rc = nghttp3_conn_submit_request(qs->h3conn, stream->stream3_id, - nva, nheader, &data_reader, data); - if(rc) { - result = CURLE_SEND_ERROR; - goto fail; - } - break; - } - default: - stream->upload_left = 0; /* nothing left to send */ - rc = nghttp3_conn_submit_request(qs->h3conn, stream->stream3_id, - nva, nheader, NULL, data); - if(rc) { - result = CURLE_SEND_ERROR; - goto fail; - } - break; - } - - Curl_safefree(nva); - - 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, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - ssize_t sent = 0; - struct connectdata *conn = data->conn; - struct quicsocket *qs = conn->quic; - curl_socket_t sockfd = conn->sock[sockindex]; - struct HTTP *stream = data->req.p.http; - - if(stream->closed) { - *curlcode = CURLE_HTTP3; - return -1; - } - - if(!stream->h3req) { - CURLcode result = http_request(data, mem, len); - if(result) { - *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 { - H3BUGF(infof(data, "ngh3_stream_send() wants to send %zd bytes", - len)); - if(!stream->upload_len) { - stream->upload_mem = mem; - stream->upload_len = len; - (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id); - } - else { - *curlcode = CURLE_AGAIN; - return -1; - } - } - - if(ng_flush_egress(data, sockfd, qs)) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - - /* 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; - return sent; -} - -static CURLcode ng_has_connected(struct Curl_easy *data, - struct connectdata *conn, int tempindex) -{ - CURLcode result = CURLE_OK; - conn->recv[FIRSTSOCKET] = ngh3_stream_recv; - conn->send[FIRSTSOCKET] = ngh3_stream_send; - conn->handler = &Curl_handler_http3; - conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->httpversion = 30; - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - conn->quic = &conn->hequic[tempindex]; - - if(conn->ssl_config.verifyhost) { -#ifdef USE_OPENSSL - X509 *server_cert; - server_cert = SSL_get_peer_certificate(conn->quic->ssl); - if(!server_cert) { - return CURLE_PEER_FAILED_VERIFICATION; - } - result = Curl_ossl_verifyhost(data, conn, server_cert); - X509_free(server_cert); - if(result) - return result; - infof(data, "Verified certificate just fine"); -#elif defined(USE_GNUTLS) - result = Curl_gtls_verifyserver(data, conn, conn->quic->ssl, FIRSTSOCKET); -#elif defined(USE_WOLFSSL) - char *snihost = Curl_ssl_snihost(data, SSL_HOST_NAME(), NULL); - if(!snihost || - (wolfSSL_check_domain_name(conn->quic->ssl, snihost) == SSL_FAILURE)) - return CURLE_PEER_FAILED_VERIFICATION; - infof(data, "Verified certificate just fine"); -#endif - } - else - infof(data, "Skipped certificate verification"); -#ifdef USE_OPENSSL - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, conn->quic->ssl); -#endif - return result; -} - -/* - * There can be multiple connection attempts going on in parallel. - */ -CURLcode Curl_quic_is_connected(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool *done) -{ - CURLcode result; - struct quicsocket *qs = &conn->hequic[sockindex]; - curl_socket_t sockfd = conn->tempsock[sockindex]; - - result = ng_process_ingress(data, sockfd, qs); - if(result) - goto error; - - result = ng_flush_egress(data, sockfd, qs); - if(result) - goto error; - - if(ngtcp2_conn_get_handshake_completed(qs->qconn)) { - result = ng_has_connected(data, conn, sockindex); - if(!result) - *done = TRUE; - } - - return result; - error: - (void)qs_disconnect(qs); - return result; - -} - -static CURLcode ng_process_ingress(struct Curl_easy *data, - curl_socket_t sockfd, - struct quicsocket *qs) -{ - ssize_t recvd; - int rv; - uint8_t buf[65536]; - size_t bufsize = sizeof(buf); - struct sockaddr_storage remote_addr; - socklen_t remote_addrlen; - ngtcp2_path path; - ngtcp2_tstamp ts = timestamp(); - ngtcp2_pkt_info pi = { 0 }; - - for(;;) { - remote_addrlen = sizeof(remote_addr); - while((recvd = recvfrom(sockfd, (char *)buf, bufsize, 0, - (struct sockaddr *)&remote_addr, - &remote_addrlen)) == -1 && - SOCKERRNO == EINTR) - ; - if(recvd == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) - break; - - failf(data, "ngtcp2: recvfrom() unexpectedly returned %zd", recvd); - return CURLE_RECV_ERROR; - } - - ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr, - qs->local_addrlen); - ngtcp2_addr_init(&path.remote, (struct sockaddr *)&remote_addr, - remote_addrlen); - - rv = ngtcp2_conn_read_pkt(qs->qconn, &path, &pi, buf, recvd, ts); - if(rv) { - 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 */ - return CURLE_PEER_FAILED_VERIFICATION; - return CURLE_RECV_ERROR; - } - } - - 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; - struct msghdr msg = {0}; - ssize_t sent; -#if defined(__linux__) && defined(UDP_SEGMENT) - uint8_t msg_ctrl[32]; - struct cmsghdr *cm; -#endif - - *psent = 0; - msg_iov.iov_base = (uint8_t *)pkt; - msg_iov.iov_len = pktlen; - 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; - size_t sent; - ngtcp2_ssize outlen; - uint8_t *outpos = qs->pktbuf; - size_t max_udp_payload_size = - ngtcp2_conn_get_max_tx_udp_payload_size(qs->qconn); - size_t path_max_udp_payload_size = - ngtcp2_conn_get_path_max_tx_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; - nghttp3_ssize veccnt; - int fin; - nghttp3_vec vec[16]; - 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(;;) { - veccnt = 0; - stream_id = -1; - fin = 0; - - if(qs->h3conn && ngtcp2_conn_get_max_data_left(qs->qconn)) { - veccnt = nghttp3_conn_writev_stream(qs->h3conn, &stream_id, &fin, vec, - sizeof(vec) / sizeof(vec[0])); - 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, 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) { - switch(outlen) { - case NGTCP2_ERR_STREAM_DATA_BLOCKED: - assert(ndatalen == -1); - nghttp3_conn_block_stream(qs->h3conn, stream_id); - continue; - case NGTCP2_ERR_STREAM_SHUT_WR: - assert(ndatalen == -1); - nghttp3_conn_shutdown_stream_write(qs->h3conn, stream_id); - continue; - case NGTCP2_ERR_WRITE_MORE: - assert(ndatalen >= 0); - rv = nghttp3_conn_add_write_offset(qs->h3conn, stream_id, ndatalen); - if(rv) { - failf(data, "nghttp3_conn_add_write_offset returned error: %s\n", - nghttp3_strerror(rv)); - return CURLE_SEND_ERROR; - } - continue; - default: - 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; - } - } - else if(ndatalen >= 0) { - rv = nghttp3_conn_add_write_offset(qs->h3conn, stream_id, ndatalen); - if(rv) { - failf(data, "nghttp3_conn_add_write_offset returned error: %s\n", - nghttp3_strerror(rv)); - return CURLE_SEND_ERROR; - } - } - - outpos += outlen; - - 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; - } - 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; - } - } - - expiry = ngtcp2_conn_get_expiry(qs->qconn); - if(expiry != UINT64_MAX) { - if(expiry <= ts) { - timeout = 0; - } - else { - timeout = expiry - ts; - if(timeout % NGTCP2_MILLISECONDS) { - timeout += NGTCP2_MILLISECONDS; - } - } - Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC); - } - - return CURLE_OK; -} - -/* - * Called from transfer.c:done_sending when we stop HTTP/3 uploading. - */ -CURLcode Curl_quic_done_sending(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - DEBUGASSERT(conn); - if(conn->handler == &Curl_handler_http3) { - /* only for HTTP/3 transfers */ - struct HTTP *stream = data->req.p.http; - struct quicsocket *qs = conn->quic; - stream->upload_done = TRUE; - (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id); - } - - 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.p.http; - Curl_dyn_free(&stream->overflow); - free(stream->h3out); - } -} - -/* - * 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.p.http; - 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 +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * 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
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * 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 <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>
+#include "vtls/gtls.h"
+#elif defined(USE_WOLFSSL)
+#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
+#include "vtls/wolfssl.h"
+#endif
+
+#include "urldata.h"
+#include "sendf.h"
+#include "strdup.h"
+#include "rand.h"
+#include "ngtcp2.h"
+#include "multiif.h"
+#include "strcase.h"
+#include "cfilters.h"
+#include "connect.h"
+#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"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+/* #define DEBUG_NGTCP2 */
+#ifdef CURLDEBUG
+#define DEBUG_HTTP3
+#endif
+#ifdef DEBUG_HTTP3
+#define H3BUGF(x) x
+#else
+#define H3BUGF(x) do { } while(0)
+#endif
+
+#define H3_ALPN_H3_29 "\x5h3-29"
+#define H3_ALPN_H3 "\x2h3"
+
+/*
+ * This holds outgoing HTTP/3 stream data that is used by nghttp3 until acked.
+ * It is used as a circular buffer. Add new bytes at the end until it reaches
+ * the far end, then start over at index 0 again.
+ */
+
+#define H3_SEND_SIZE (256*1024)
+struct h3out {
+ uint8_t buf[H3_SEND_SIZE];
+ size_t used; /* number of bytes used in the buffer */
+ size_t windex; /* index in the buffer where to start writing the next
+ data block */
+};
+
+#define QUIC_MAX_STREAMS (256*1024)
+#define QUIC_MAX_DATA (1*1024*1024)
+#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS)
+
+#ifdef USE_OPENSSL
+#define QUIC_CIPHERS \
+ "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \
+ "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
+#define QUIC_GROUPS "P-256:X25519:P-384:P-521"
+#elif defined(USE_GNUTLS)
+#define QUIC_PRIORITY \
+ "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \
+ "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \
+ "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \
+ "%DISABLE_TLS13_COMPAT_MODE"
+#elif defined(USE_WOLFSSL)
+#define QUIC_CIPHERS \
+ "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \
+ "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
+#define QUIC_GROUPS "P-256:P-384:P-521"
+#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);
+static CURLcode ng_flush_egress(struct Curl_easy *data, int sockfd,
+ struct quicsocket *qs);
+static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
+ uint64_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();
+ return ct.tv_sec * NGTCP2_SECONDS + ct.tv_usec * NGTCP2_MICROSECONDS;
+}
+
+#ifdef DEBUG_NGTCP2
+static void quic_printf(void *user_data, const char *fmt, ...)
+{
+ va_list ap;
+ (void)user_data; /* TODO, use this to do infof() instead long-term */
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+}
+#endif
+
+static void qlog_callback(void *user_data, uint32_t flags,
+ const void *data, size_t datalen)
+{
+ struct quicsocket *qs = (struct quicsocket *)user_data;
+ (void)flags;
+ if(qs->qlogfd != -1) {
+ ssize_t rc = write(qs->qlogfd, data, datalen);
+ if(rc == -1) {
+ /* on write error, stop further write attempts */
+ close(qs->qlogfd);
+ qs->qlogfd = -1;
+ }
+ }
+
+}
+
+static void quic_settings(struct quicsocket *qs,
+ uint64_t stream_buffer_size)
+{
+ ngtcp2_settings *s = &qs->settings;
+ ngtcp2_transport_params *t = &qs->transport_params;
+ ngtcp2_settings_default(s);
+ ngtcp2_transport_params_default(t);
+#ifdef DEBUG_NGTCP2
+ s->log_printf = quic_printf;
+#else
+ s->log_printf = NULL;
+#endif
+ s->initial_ts = timestamp();
+ t->initial_max_stream_data_bidi_local = stream_buffer_size;
+ t->initial_max_stream_data_bidi_remote = QUIC_MAX_STREAMS;
+ t->initial_max_stream_data_uni = QUIC_MAX_STREAMS;
+ t->initial_max_data = QUIC_MAX_DATA;
+ t->initial_max_streams_bidi = 1;
+ t->initial_max_streams_uni = 3;
+ t->max_idle_timeout = QUIC_IDLE_TIMEOUT;
+ if(qs->qlogfd != -1) {
+ s->qlog.write = qlog_callback;
+ }
+}
+
+#ifdef USE_OPENSSL
+static void keylog_callback(const SSL *ssl, const char *line)
+{
+ (void)ssl;
+ Curl_tls_keylog_write_line(line);
+}
+#elif defined(USE_GNUTLS)
+static int keylog_callback(gnutls_session_t session, const char *label,
+ const gnutls_datum_t *secret)
+{
+ gnutls_datum_t crandom;
+ gnutls_datum_t srandom;
+
+ gnutls_session_get_random(session, &crandom, &srandom);
+ if(crandom.size != 32) {
+ return -1;
+ }
+
+ Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size);
+ return 0;
+}
+#elif defined(USE_WOLFSSL)
+#if defined(HAVE_SECRET_CALLBACK)
+static void keylog_callback(const WOLFSSL *ssl, const char *line)
+{
+ (void)ssl;
+ Curl_tls_keylog_write_line(line);
+}
+#endif
+#endif
+
+static int init_ngh3_conn(struct quicsocket *qs);
+
+#ifdef USE_OPENSSL
+static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
+{
+ struct connectdata *conn = data->conn;
+ SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
+
+#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));
+ failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer);
+ return NULL;
+ }
+
+ if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) {
+ failf(data, "SSL_CTX_set1_groups_list failed");
+ return NULL;
+ }
+#endif
+
+ /* Open the file if a TLS or QUIC backend has not done this before. */
+ Curl_tls_keylog_open();
+ if(Curl_tls_keylog_enabled()) {
+ SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+ }
+
+ if(conn->ssl_config.verifypeer) {
+ 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");
+ }
+#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;
+}
+
+static CURLcode quic_set_client_cert(struct Curl_easy *data,
+ struct quicsocket *qs)
+{
+ SSL_CTX *ssl_ctx = qs->sslctx;
+ const struct ssl_config_data *ssl_config;
+
+ ssl_config = Curl_ssl_get_config(data, FIRSTSOCKET);
+ DEBUGASSERT(ssl_config);
+
+ if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob
+ || ssl_config->cert_type) {
+ return Curl_ossl_set_client_cert(
+ data, ssl_ctx, ssl_config->primary.clientcert,
+ ssl_config->primary.cert_blob, ssl_config->cert_type,
+ ssl_config->key, ssl_config->key_blob,
+ ssl_config->key_type, ssl_config->key_passwd);
+ }
+
+ return CURLE_OK;
+}
+
+/** SSL callbacks ***/
+
+static CURLcode quic_init_ssl(struct quicsocket *qs,
+ struct Curl_easy *data,
+ struct connectdata *conn)
+{
+ const uint8_t *alpn = NULL;
+ size_t alpnlen = 0;
+ /* this will need some attention when HTTPS proxy over QUIC get fixed */
+ const char * const hostname = qs->conn->host.name;
+
+ (void)data;
+ (void)conn;
+ DEBUGASSERT(!qs->ssl);
+ qs->ssl = SSL_new(qs->sslctx);
+
+ SSL_set_app_data(qs->ssl, &qs->conn_ref);
+ SSL_set_connect_state(qs->ssl);
+ SSL_set_quic_use_legacy_codepoint(qs->ssl, 0);
+
+ alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3;
+ alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1;
+ if(alpn)
+ SSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen);
+
+ /* set SNI */
+ SSL_set_tlsext_host_name(qs->ssl, hostname);
+ return CURLE_OK;
+}
+#elif defined(USE_GNUTLS)
+static CURLcode quic_init_ssl(struct quicsocket *qs,
+ struct Curl_easy *data,
+ struct connectdata *conn)
+{
+ CURLcode result;
+ gnutls_datum_t alpn[2];
+ /* this will need some attention when HTTPS proxy over QUIC get fixed */
+ const char * const hostname = qs->conn->host.name;
+ long * const pverifyresult = &data->set.ssl.certverifyresult;
+ int rc;
+
+ DEBUGASSERT(qs->gtls == NULL);
+ qs->gtls = calloc(1, sizeof(*(qs->gtls)));
+ if(!qs->gtls)
+ return CURLE_OUT_OF_MEMORY;
+
+ result = gtls_client_init(data, &conn->ssl_config, &data->set.ssl,
+ hostname, qs->gtls, pverifyresult);
+ if(result)
+ return result;
+
+ gnutls_session_set_ptr(qs->gtls->session, &qs->conn_ref);
+
+ if(ngtcp2_crypto_gnutls_configure_client_session(qs->gtls->session) != 0) {
+ H3BUGF(fprintf(stderr,
+ "ngtcp2_crypto_gnutls_configure_client_session failed\n"));
+ return CURLE_QUIC_CONNECT_ERROR;
+ }
+
+ rc = gnutls_priority_set_direct(qs->gtls->session, QUIC_PRIORITY, NULL);
+ if(rc < 0) {
+ H3BUGF(fprintf(stderr, "gnutls_priority_set_direct failed: %s\n",
+ gnutls_strerror(rc)));
+ return CURLE_QUIC_CONNECT_ERROR;
+ }
+
+ /* Open the file if a TLS or QUIC backend has not done this before. */
+ Curl_tls_keylog_open();
+ if(Curl_tls_keylog_enabled()) {
+ gnutls_session_set_keylog_function(qs->gtls->session, keylog_callback);
+ }
+
+ /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */
+ alpn[0].data = (unsigned char *)H3_ALPN_H3_29 + 1;
+ alpn[0].size = sizeof(H3_ALPN_H3_29) - 2;
+ alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1;
+ alpn[1].size = sizeof(H3_ALPN_H3) - 2;
+
+ gnutls_alpn_set_protocols(qs->gtls->session, alpn, 2, GNUTLS_ALPN_MANDATORY);
+
+ return CURLE_OK;
+}
+#elif defined(USE_WOLFSSL)
+
+static WOLFSSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
+{
+ struct connectdata *conn = data->conn;
+ WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
+
+ if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) {
+ failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");
+ return NULL;
+ }
+
+ wolfSSL_CTX_set_default_verify_paths(ssl_ctx);
+
+ if(wolfSSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) {
+ 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;
+ }
+
+ if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) {
+ failf(data, "SSL_CTX_set1_groups_list failed");
+ return NULL;
+ }
+
+ /* Open the file if a TLS or QUIC backend has not done this before. */
+ Curl_tls_keylog_open();
+ if(Curl_tls_keylog_enabled()) {
+#if defined(HAVE_SECRET_CALLBACK)
+ wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+#else
+ failf(data, "wolfSSL was built without keylog callback");
+ return NULL;
+#endif
+ }
+
+ if(conn->ssl_config.verifypeer) {
+ const char * const ssl_cafile = conn->ssl_config.CAfile;
+ const char * const ssl_capath = conn->ssl_config.CApath;
+
+ if(ssl_cafile || ssl_capath) {
+ wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+ /* tell wolfSSL where to find CA certificates that are used to verify
+ the server's certificate. */
+ if(!wolfSSL_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");
+ }
+#ifdef CURL_CA_FALLBACK
+ else {
+ /* verifying the peer without any CA certificates won't work so
+ use wolfssl's built-in default as fallback */
+ wolfSSL_CTX_set_default_verify_paths(ssl_ctx);
+ }
+#endif
+ }
+ else {
+ wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
+ }
+
+ return ssl_ctx;
+}
+
+/** SSL callbacks ***/
+
+static CURLcode quic_init_ssl(struct quicsocket *qs,
+ struct Curl_easy *data,
+ struct connectdata *conn)
+{
+ const uint8_t *alpn = NULL;
+ size_t alpnlen = 0;
+ /* this will need some attention when HTTPS proxy over QUIC get fixed */
+ const char * const hostname = qs->conn->host.name;
+
+ (void)data;
+ (void)conn;
+ DEBUGASSERT(!qs->ssl);
+ qs->ssl = SSL_new(qs->sslctx);
+
+ wolfSSL_set_app_data(qs->ssl, &qs->conn_ref);
+ wolfSSL_set_connect_state(qs->ssl);
+ wolfSSL_set_quic_use_legacy_codepoint(qs->ssl, 0);
+
+ alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3;
+ alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1;
+ if(alpn)
+ wolfSSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen);
+
+ /* set SNI */
+ wolfSSL_UseSNI(qs->ssl, WOLFSSL_SNI_HOST_NAME,
+ hostname, (unsigned short)strlen(hostname));
+
+ return CURLE_OK;
+}
+#endif /* defined(USE_WOLFSSL) */
+
+static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data)
+{
+ (void)user_data;
+ (void)tconn;
+ 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, uint32_t flags,
+ int64_t stream_id, uint64_t offset,
+ const uint8_t *buf, size_t buflen,
+ void *user_data, void *stream_user_data)
+{
+ struct quicsocket *qs = (struct quicsocket *)user_data;
+ nghttp3_ssize nconsumed;
+ int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0;
+ (void)offset;
+ (void)stream_user_data;
+
+ 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;
+ }
+
+ /* 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);
+
+ return 0;
+}
+
+static int
+cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id,
+ uint64_t offset, uint64_t datalen, void *user_data,
+ void *stream_user_data)
+{
+ struct quicsocket *qs = (struct quicsocket *)user_data;
+ int rv;
+ (void)stream_id;
+ (void)tconn;
+ (void)offset;
+ (void)datalen;
+ (void)stream_user_data;
+
+ rv = nghttp3_conn_add_ack_offset(qs->h3conn, stream_id, datalen);
+ if(rv) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags,
+ int64_t stream_id, uint64_t app_error_code,
+ void *user_data, void *stream_user_data)
+{
+ struct quicsocket *qs = (struct quicsocket *)user_data;
+ int rv;
+ (void)tconn;
+ (void)stream_user_data;
+ /* stream is closed... */
+
+ if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
+ app_error_code = NGHTTP3_H3_NO_ERROR;
+ }
+
+ 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;
+ }
+
+ return 0;
+}
+
+static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id,
+ uint64_t final_size, uint64_t app_error_code,
+ void *user_data, void *stream_user_data)
+{
+ struct quicsocket *qs = (struct quicsocket *)user_data;
+ int rv;
+ (void)tconn;
+ (void)final_size;
+ (void)app_error_code;
+ (void)stream_user_data;
+
+ rv = nghttp3_conn_shutdown_stream_read(qs->h3conn, stream_id);
+ if(rv) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data)
+{
+ struct quicsocket *qs = (struct quicsocket *)user_data;
+ int rv;
+ (void)tconn;
+ (void)app_error_code;
+ (void)stream_user_data;
+
+ rv = nghttp3_conn_shutdown_stream_read(qs->h3conn, stream_id);
+ if(rv) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn,
+ uint64_t max_streams,
+ void *user_data)
+{
+ (void)tconn;
+ (void)max_streams;
+ (void)user_data;
+
+ return 0;
+}
+
+static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id,
+ uint64_t max_data, void *user_data,
+ void *stream_user_data)
+{
+ struct quicsocket *qs = (struct quicsocket *)user_data;
+ int rv;
+ (void)tconn;
+ (void)max_data;
+ (void)stream_user_data;
+
+ rv = nghttp3_conn_unblock_stream(qs->h3conn, stream_id);
+ if(rv) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static void cb_rand(uint8_t *dest, size_t destlen,
+ const ngtcp2_rand_ctx *rand_ctx)
+{
+ CURLcode result;
+ (void)rand_ctx;
+
+ result = Curl_rand(NULL, dest, destlen);
+ if(result) {
+ /* cb_rand is only used for non-cryptographic context. If Curl_rand
+ failed, just fill 0 and call it *random*. */
+ memset(dest, 0, destlen);
+ }
+}
+
+static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid,
+ uint8_t *token, size_t cidlen,
+ void *user_data)
+{
+ CURLcode result;
+ (void)tconn;
+ (void)user_data;
+
+ result = Curl_rand(NULL, cid->data, cidlen);
+ if(result)
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ cid->datalen = cidlen;
+
+ result = Curl_rand(NULL, token, NGTCP2_STATELESS_RESET_TOKENLEN);
+ if(result)
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+
+ 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 */
+ ngtcp2_crypto_recv_crypto_data_cb,
+ cb_handshake_completed,
+ NULL, /* recv_version_negotiation */
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ ngtcp2_crypto_hp_mask_cb,
+ cb_recv_stream_data,
+ cb_acked_stream_data_offset,
+ NULL, /* stream_open */
+ cb_stream_close,
+ NULL, /* recv_stateless_reset */
+ ngtcp2_crypto_recv_retry_cb,
+ cb_extend_max_local_streams_bidi,
+ NULL, /* extend_max_local_streams_uni */
+ cb_rand,
+ cb_get_new_connection_id,
+ NULL, /* remove_connection_id */
+ ngtcp2_crypto_update_key_cb, /* update_key */
+ NULL, /* path_validation */
+ NULL, /* select_preferred_addr */
+ cb_stream_reset,
+ NULL, /* extend_max_remote_streams_bidi */
+ NULL, /* extend_max_remote_streams_uni */
+ cb_extend_max_stream_data,
+ NULL, /* dcid_status */
+ NULL, /* handshake_confirmed */
+ NULL, /* recv_new_token */
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ NULL, /* recv_datagram */
+ NULL, /* ack_datagram */
+ NULL, /* lost_datagram */
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ cb_stream_stop_sending,
+ NULL, /* version_negotiation */
+ cb_recv_rx_key,
+ NULL, /* recv_tx_key */
+ NULL, /* early_data_rejected */
+};
+
+/*
+ * Might be called twice for happy eyeballs.
+ */
+CURLcode Curl_quic_connect(struct Curl_easy *data,
+ struct connectdata *conn,
+ curl_socket_t sockfd,
+ int sockindex,
+ const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ int rc;
+ int rv;
+ CURLcode result;
+ ngtcp2_path path; /* TODO: this must be initialized properly */
+ struct quicsocket *qs = &conn->hequic[sockindex];
+ char ipbuf[40];
+ int port;
+ int qfd;
+
+ if(qs->conn)
+ Curl_quic_disconnect(data, conn, sockindex);
+ qs->conn = conn;
+
+ /* extract the used address as a string */
+ if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) {
+ char buffer[STRERROR_LEN];
+ failf(data, "ssrem inet_ntop() failed with errno %d: %s",
+ SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+
+ infof(data, "Connect socket %d over QUIC to %s:%d",
+ sockfd, ipbuf, port);
+
+ qs->version = NGTCP2_PROTO_VER_MAX;
+#ifdef USE_OPENSSL
+ 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;
+#elif defined(USE_WOLFSSL)
+ qs->sslctx = quic_ssl_ctx(data);
+ if(!qs->sslctx)
+ return CURLE_QUIC_CONNECT_ERROR;
+#endif
+
+ result = quic_init_ssl(qs, data, conn);
+ if(result)
+ return result;
+
+ qs->dcid.datalen = NGTCP2_MAX_CIDLEN;
+ result = Curl_rand(data, qs->dcid.data, NGTCP2_MAX_CIDLEN);
+ if(result)
+ return result;
+
+ qs->scid.datalen = NGTCP2_MAX_CIDLEN;
+ result = Curl_rand(data, qs->scid.data, NGTCP2_MAX_CIDLEN);
+ if(result)
+ return result;
+
+ (void)Curl_qlogdir(data, qs->scid.data, NGTCP2_MAX_CIDLEN, &qfd);
+ qs->qlogfd = qfd; /* -1 if failure above */
+ quic_settings(qs, data->set.buffer_size);
+
+ qs->local_addrlen = sizeof(qs->local_addr);
+ rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr,
+ &qs->local_addrlen);
+ if(rv == -1)
+ return CURLE_QUIC_CONNECT_ERROR;
+
+ ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr,
+ qs->local_addrlen);
+ ngtcp2_addr_init(&path.remote, addr, addrlen);
+
+ rc = ngtcp2_conn_client_new(&qs->qconn, &qs->dcid, &qs->scid, &path,
+ NGTCP2_PROTO_VER_V1, &ng_callbacks,
+ &qs->settings, &qs->transport_params, NULL, qs);
+ if(rc)
+ return CURLE_QUIC_CONNECT_ERROR;
+
+#ifdef USE_GNUTLS
+ ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->gtls->session);
+#else
+ ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->ssl);
+#endif
+
+ 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;
+}
+
+/*
+ * Store ngtcp2 version info in this buffer.
+ */
+void Curl_quic_ver(char *p, size_t len)
+{
+ const ngtcp2_info *ng2 = ngtcp2_version(0);
+ const nghttp3_info *ht3 = nghttp3_version(0);
+ (void)msnprintf(p, len, "ngtcp2/%s nghttp3/%s",
+ ng2->version_str, ht3->version_str);
+}
+
+static int ng_getsock(struct Curl_easy *data, struct connectdata *conn,
+ curl_socket_t *socks)
+{
+ 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];
+
+ /* in an HTTP/2 connection we can basically always get a frame so we should
+ always be ready for one */
+ 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 &&
+ (!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;
+}
+
+static void qs_disconnect(struct quicsocket *qs)
+{
+ char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE];
+ ngtcp2_tstamp ts;
+ ngtcp2_ssize rc;
+
+ if(!qs->conn) /* already closed */
+ return;
+ ts = timestamp();
+ rc = ngtcp2_conn_write_connection_close(qs->qconn, NULL, /* path */
+ NULL, /* pkt_info */
+ (uint8_t *)buffer, sizeof(buffer),
+ &qs->last_error, 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);
+ qs->qlogfd = -1;
+ }
+#ifdef USE_OPENSSL
+ if(qs->ssl)
+ SSL_free(qs->ssl);
+ qs->ssl = NULL;
+ SSL_CTX_free(qs->sslctx);
+#elif defined(USE_GNUTLS)
+ if(qs->gtls) {
+ if(qs->gtls->cred)
+ gnutls_certificate_free_credentials(qs->gtls->cred);
+ if(qs->gtls->session)
+ gnutls_deinit(qs->gtls->session);
+ free(qs->gtls);
+ qs->gtls = NULL;
+ }
+#elif defined(USE_WOLFSSL)
+ if(qs->ssl)
+ wolfSSL_free(qs->ssl);
+ qs->ssl = NULL;
+ wolfSSL_CTX_free(qs->sslctx);
+#endif
+ free(qs->pktbuf);
+ nghttp3_conn_del(qs->h3conn);
+ ngtcp2_conn_del(qs->qconn);
+}
+
+void Curl_quic_disconnect(struct Curl_easy *data,
+ struct connectdata *conn,
+ int tempindex)
+{
+ (void)data;
+ if(conn->transport == TRNSPRT_QUIC)
+ qs_disconnect(&conn->hequic[tempindex]);
+}
+
+static CURLcode ng_disconnect(struct Curl_easy *data,
+ struct connectdata *conn,
+ bool dead_connection)
+{
+ (void)dead_connection;
+ Curl_quic_disconnect(data, conn, 0);
+ Curl_quic_disconnect(data, conn, 1);
+ return CURLE_OK;
+}
+
+static unsigned int ng_conncheck(struct Curl_easy *data,
+ struct connectdata *conn,
+ unsigned int checks_to_perform)
+{
+ (void)data;
+ (void)conn;
+ (void)checks_to_perform;
+ return CONNRESULT_NONE;
+}
+
+static const struct Curl_handler Curl_handler_http3 = {
+ "HTTPS", /* scheme */
+ ZERO_NULL, /* setup_connection */
+ Curl_http, /* do_it */
+ Curl_http_done, /* done */
+ ZERO_NULL, /* do_more */
+ ZERO_NULL, /* connect_it */
+ ZERO_NULL, /* connecting */
+ ZERO_NULL, /* doing */
+ ng_getsock, /* proto_getsock */
+ ng_getsock, /* doing_getsock */
+ ZERO_NULL, /* domore_getsock */
+ ng_getsock, /* perform_getsock */
+ ng_disconnect, /* disconnect */
+ ZERO_NULL, /* readwrite */
+ ng_conncheck, /* connection_check */
+ ZERO_NULL, /* attach connection */
+ PORT_HTTP, /* defport */
+ CURLPROTO_HTTPS, /* protocol */
+ CURLPROTO_HTTP, /* family */
+ PROTOPT_SSL | PROTOPT_STREAM /* flags */
+};
+
+static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data)
+{
+ struct Curl_easy *data = stream_user_data;
+ struct HTTP *stream = data->req.p.http;
+ (void)conn;
+ (void)stream_id;
+ (void)app_error_code;
+ (void)user_data;
+ H3BUGF(infof(data, "cb_h3_stream_close CALLED"));
+
+ stream->closed = TRUE;
+ stream->error3 = app_error_code;
+ 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;
+}
+
+/*
+ * 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 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);
+ 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 = Curl_dyn_addn(&stream->overflow, buf, ncopy);
+ return result;
+}
+
+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.p.http;
+ CURLcode result = CURLE_OK;
+ (void)conn;
+
+ result = write_data(stream, buf, buflen);
+ if(result) {
+ return -1;
+ }
+ stream->unacked_window += buflen;
+ (void)stream_id;
+ (void)user_data;
+ return 0;
+}
+
+static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
+ size_t consumed, void *user_data,
+ void *stream_user_data)
+{
+ 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;
+}
+
+/* Decode HTTP status code. Returns -1 if no valid status code was
+ decoded. (duplicate from http2.c) */
+static int decode_status_code(const uint8_t *value, size_t len)
+{
+ int i;
+ int res;
+
+ if(len != 3) {
+ return -1;
+ }
+
+ res = 0;
+
+ for(i = 0; i < 3; ++i) {
+ char c = value[i];
+
+ if(c < '0' || c > '9') {
+ return -1;
+ }
+
+ res *= 10;
+ res += c - '0';
+ }
+
+ return res;
+}
+
+static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
+ int fin, void *user_data, void *stream_user_data)
+{
+ struct Curl_easy *data = stream_user_data;
+ struct HTTP *stream = data->req.p.http;
+ CURLcode result = CURLE_OK;
+ (void)conn;
+ (void)stream_id;
+ (void)user_data;
+ (void)fin;
+
+ /* add a CRLF only if we've received some headers */
+ if(stream->firstheader) {
+ result = write_data(stream, "\r\n", 2);
+ if(result) {
+ return -1;
+ }
+ }
+
+ if(stream->status_code / 100 != 1) {
+ stream->bodystarted = TRUE;
+ }
+ return 0;
+}
+
+static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
+ int32_t token, nghttp3_rcbuf *name,
+ nghttp3_rcbuf *value, uint8_t flags,
+ void *user_data, void *stream_user_data)
+{
+ nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name);
+ nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value);
+ struct Curl_easy *data = stream_user_data;
+ struct HTTP *stream = data->req.p.http;
+ CURLcode result = CURLE_OK;
+ (void)conn;
+ (void)stream_id;
+ (void)token;
+ (void)flags;
+ (void)user_data;
+
+ if(token == NGHTTP3_QPACK_TOKEN__STATUS) {
+ char line[14]; /* status line is always 13 characters long */
+ size_t ncopy;
+ stream->status_code = decode_status_code(h3val.base, h3val.len);
+ DEBUGASSERT(stream->status_code != -1);
+ ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n",
+ stream->status_code);
+ result = write_data(stream, line, ncopy);
+ if(result) {
+ return -1;
+ }
+ }
+ else {
+ /* store as an HTTP1-style header */
+ result = write_data(stream, h3name.base, h3name.len);
+ if(result) {
+ return -1;
+ }
+ result = write_data(stream, ": ", 2);
+ if(result) {
+ return -1;
+ }
+ result = write_data(stream, h3val.base, h3val.len);
+ if(result) {
+ return -1;
+ }
+ result = write_data(stream, "\r\n", 2);
+ if(result) {
+ return -1;
+ }
+ }
+
+ stream->firstheader = TRUE;
+ return 0;
+}
+
+static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data)
+{
+ struct quicsocket *qs = user_data;
+ int rv;
+ (void)conn;
+ (void)stream_user_data;
+
+ rv = ngtcp2_conn_shutdown_stream_read(qs->qconn, stream_id, app_error_code);
+ if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ struct quicsocket *qs = user_data;
+ int rv;
+ (void)conn;
+ (void)stream_user_data;
+
+ rv = ngtcp2_conn_shutdown_stream_write(qs->qconn, stream_id, app_error_code);
+ if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static nghttp3_callbacks ngh3_callbacks = {
+ cb_h3_acked_stream_data, /* acked_stream_data */
+ cb_h3_stream_close,
+ cb_h3_recv_data,
+ cb_h3_deferred_consume,
+ NULL, /* begin_headers */
+ cb_h3_recv_header,
+ cb_h3_end_headers,
+ NULL, /* begin_trailers */
+ cb_h3_recv_header,
+ NULL, /* end_trailers */
+ cb_h3_stop_sending,
+ NULL, /* end_stream */
+ cb_h3_reset_stream,
+ NULL /* shutdown */
+};
+
+static int init_ngh3_conn(struct quicsocket *qs)
+{
+ CURLcode result;
+ int rc;
+ int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id;
+
+ if(ngtcp2_conn_get_max_local_streams_uni(qs->qconn) < 3) {
+ return CURLE_QUIC_CONNECT_ERROR;
+ }
+
+ nghttp3_settings_default(&qs->h3settings);
+
+ rc = nghttp3_conn_client_new(&qs->h3conn,
+ &ngh3_callbacks,
+ &qs->h3settings,
+ nghttp3_mem_default(),
+ qs);
+ if(rc) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+
+ rc = ngtcp2_conn_open_uni_stream(qs->qconn, &ctrl_stream_id, NULL);
+ if(rc) {
+ result = CURLE_QUIC_CONNECT_ERROR;
+ goto fail;
+ }
+
+ rc = nghttp3_conn_bind_control_stream(qs->h3conn, ctrl_stream_id);
+ if(rc) {
+ result = CURLE_QUIC_CONNECT_ERROR;
+ goto fail;
+ }
+
+ rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_enc_stream_id, NULL);
+ if(rc) {
+ result = CURLE_QUIC_CONNECT_ERROR;
+ goto fail;
+ }
+
+ rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_dec_stream_id, NULL);
+ if(rc) {
+ result = CURLE_QUIC_CONNECT_ERROR;
+ goto fail;
+ }
+
+ rc = nghttp3_conn_bind_qpack_streams(qs->h3conn, qpack_enc_stream_id,
+ qpack_dec_stream_id);
+ if(rc) {
+ result = CURLE_QUIC_CONNECT_ERROR;
+ goto fail;
+ }
+
+ return CURLE_OK;
+ fail:
+
+ return result;
+}
+
+static Curl_recv ngh3_stream_recv;
+static Curl_send ngh3_stream_send;
+
+static size_t drain_overflow_buffer(struct HTTP *stream)
+{
+ size_t overlen = Curl_dyn_len(&stream->overflow);
+ size_t ncopy = CURLMIN(overlen, stream->len);
+ if(ncopy > 0) {
+ memcpy(stream->mem, Curl_dyn_ptr(&stream->overflow), ncopy);
+ stream->len -= ncopy;
+ stream->mem += ncopy;
+ stream->memlen += ncopy;
+ 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;
+}
+
+/* incoming data frames on the h3 stream */
+static ssize_t ngh3_stream_recv(struct Curl_easy *data,
+ int sockindex,
+ char *buf,
+ size_t buffersize,
+ CURLcode *curlcode)
+{
+ struct connectdata *conn = data->conn;
+ curl_socket_t sockfd = conn->sock[sockindex];
+ struct HTTP *stream = data->req.p.http;
+ struct quicsocket *qs = conn->quic;
+
+ if(!stream->memlen) {
+ /* remember where to store incoming data for this stream and how big the
+ buffer is */
+ stream->mem = buf;
+ stream->len = buffersize;
+ }
+ /* 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(data, sockfd, qs)) {
+ *curlcode = CURLE_RECV_ERROR;
+ return -1;
+ }
+ if(ng_flush_egress(data, sockfd, qs)) {
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+ }
+
+ if(stream->memlen) {
+ ssize_t memlen = stream->memlen;
+ /* data arrived */
+ *curlcode = CURLE_OK;
+ /* reset to allow more data to come */
+ stream->memlen = 0;
+ stream->mem = buf;
+ stream->len = buffersize;
+ /* 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(data, sockfd, qs)) {
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+ }
+ return memlen;
+ }
+
+ if(stream->closed) {
+ if(stream->error3 != NGHTTP3_H3_NO_ERROR) {
+ failf(data,
+ "HTTP/3 stream %" PRId64 " was not closed cleanly: (err %" PRIu64
+ ")",
+ stream->stream3_id, stream->error3);
+ *curlcode = CURLE_HTTP3;
+ return -1;
+ }
+
+ if(!stream->bodystarted) {
+ failf(data,
+ "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting"
+ " all response header fields, treated as error",
+ stream->stream3_id);
+ *curlcode = CURLE_HTTP3;
+ return -1;
+ }
+
+ *curlcode = CURLE_OK;
+ return 0;
+ }
+
+ infof(data, "ngh3_stream_recv returns 0 bytes and EAGAIN");
+ *curlcode = CURLE_AGAIN;
+ return -1;
+}
+
+/* this amount of data has now been acked on this stream */
+static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t datalen, void *user_data,
+ void *stream_user_data)
+{
+ struct Curl_easy *data = stream_user_data;
+ struct HTTP *stream = data->req.p.http;
+ (void)user_data;
+
+ if(!data->set.postfields) {
+ stream->h3out->used -= datalen;
+ H3BUGF(infof(data,
+ "cb_h3_acked_stream_data, %zd bytes, %zd left unacked",
+ datalen, stream->h3out->used));
+ DEBUGASSERT(stream->h3out->used < H3_SEND_SIZE);
+
+ if(stream->h3out->used == 0) {
+ int rv = nghttp3_conn_resume_stream(conn, stream_id);
+ if(rv) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ }
+ }
+ return 0;
+}
+
+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;
+ struct HTTP *stream = data->req.p.http;
+ (void)conn;
+ (void)stream_id;
+ (void)user_data;
+ (void)veccnt;
+
+ if(data->set.postfields) {
+ vec[0].base = data->set.postfields;
+ vec[0].len = data->state.infilesize;
+ *pflags = NGHTTP3_DATA_FLAG_EOF;
+ 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
+ delete it. Append the data at the end of the h3out buffer. Since we can
+ only return consecutive data, copy the amount that fits and the next
+ part comes in next invoke. */
+ struct h3out *out = stream->h3out;
+ if(nread + out->windex > H3_SEND_SIZE)
+ nread = H3_SEND_SIZE - out->windex;
+
+ memcpy(&out->buf[out->windex], stream->upload_mem, nread);
+
+ /* that's the chunk we return to nghttp3 */
+ vec[0].base = &out->buf[out->windex];
+ vec[0].len = nread;
+
+ out->windex += nread;
+ out->used += nread;
+
+ if(out->windex == H3_SEND_SIZE)
+ out->windex = 0; /* wrap */
+ stream->upload_mem += nread;
+ stream->upload_len -= nread;
+ if(data->state.infilesize != -1) {
+ stream->upload_left -= nread;
+ if(!stream->upload_left)
+ *pflags = NGHTTP3_DATA_FLAG_EOF;
+ }
+ H3BUGF(infof(data, "cb_h3_readfunction %zd bytes%s (at %zd unacked)",
+ nread, *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"",
+ out->used));
+ }
+ if(stream->upload_done && !stream->upload_len &&
+ (stream->upload_left <= 0)) {
+ H3BUGF(infof(data, "cb_h3_readfunction sets EOF"));
+ *pflags = NGHTTP3_DATA_FLAG_EOF;
+ return nread ? 1 : 0;
+ }
+ else if(!nread) {
+ return NGHTTP3_ERR_WOULDBLOCK;
+ }
+ return 1;
+}
+
+/* Index where :authority header field will appear in request header
+ field list. */
+#define AUTHORITY_DST_IDX 3
+
+static CURLcode http_request(struct Curl_easy *data, const void *mem,
+ size_t len)
+{
+ struct connectdata *conn = data->conn;
+ struct HTTP *stream = data->req.p.http;
+ size_t nheader;
+ 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) {
+ failf(data, "can get bidi streams");
+ result = CURLE_SEND_ERROR;
+ goto fail;
+ }
+
+ stream->stream3_id = stream3_id;
+ stream->h3req = TRUE; /* senf off! */
+ Curl_dyn_init(&stream->overflow, CURL_MAX_READ_SIZE);
+
+ result = Curl_pseudo_headers(data, mem, len, &hreq);
+ if(result)
+ goto fail;
+ nheader = hreq->entries;
+
+ nva = malloc(sizeof(nghttp3_nv) * nheader);
+ if(!nva) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ 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;
+ nva[i].flags = NGHTTP3_NV_FLAG_NONE;
+ }
+ }
+
+ switch(data->state.httpreq) {
+ case HTTPREQ_POST:
+ case HTTPREQ_POST_FORM:
+ case HTTPREQ_POST_MIME:
+ case HTTPREQ_PUT: {
+ nghttp3_data_reader data_reader;
+ if(data->state.infilesize != -1)
+ stream->upload_left = data->state.infilesize;
+ else
+ /* data sending without specifying the data amount up front */
+ stream->upload_left = -1; /* unknown, but not zero */
+
+ data_reader.read_data = cb_h3_readfunction;
+
+ h3out = calloc(sizeof(struct h3out), 1);
+ if(!h3out) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ stream->h3out = h3out;
+
+ rc = nghttp3_conn_submit_request(qs->h3conn, stream->stream3_id,
+ nva, nheader, &data_reader, data);
+ if(rc) {
+ result = CURLE_SEND_ERROR;
+ goto fail;
+ }
+ break;
+ }
+ default:
+ stream->upload_left = 0; /* nothing left to send */
+ rc = nghttp3_conn_submit_request(qs->h3conn, stream->stream3_id,
+ nva, nheader, NULL, data);
+ if(rc) {
+ result = CURLE_SEND_ERROR;
+ goto fail;
+ }
+ break;
+ }
+
+ Curl_safefree(nva);
+
+ 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,
+ int sockindex,
+ const void *mem,
+ size_t len,
+ CURLcode *curlcode)
+{
+ ssize_t sent = 0;
+ struct connectdata *conn = data->conn;
+ struct quicsocket *qs = conn->quic;
+ curl_socket_t sockfd = conn->sock[sockindex];
+ struct HTTP *stream = data->req.p.http;
+
+ if(stream->closed) {
+ *curlcode = CURLE_HTTP3;
+ return -1;
+ }
+
+ if(!stream->h3req) {
+ CURLcode result = http_request(data, mem, len);
+ if(result) {
+ *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 {
+ H3BUGF(infof(data, "ngh3_stream_send() wants to send %zd bytes",
+ len));
+ if(!stream->upload_len) {
+ stream->upload_mem = mem;
+ stream->upload_len = len;
+ (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id);
+ }
+ else {
+ *curlcode = CURLE_AGAIN;
+ return -1;
+ }
+ }
+
+ if(ng_flush_egress(data, sockfd, qs)) {
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+ }
+
+ /* 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;
+ return sent;
+}
+
+static CURLcode ng_has_connected(struct Curl_easy *data,
+ struct connectdata *conn, int tempindex)
+{
+ CURLcode result = CURLE_OK;
+ const char *hostname, *disp_hostname;
+ int port;
+ char *snihost;
+
+ Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &disp_hostname, &port);
+ snihost = Curl_ssl_snihost(data, hostname, NULL);
+ if(!snihost)
+ return CURLE_PEER_FAILED_VERIFICATION;
+
+ conn->recv[FIRSTSOCKET] = ngh3_stream_recv;
+ conn->send[FIRSTSOCKET] = ngh3_stream_send;
+ conn->handler = &Curl_handler_http3;
+ conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+ conn->httpversion = 30;
+ conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+ conn->quic = &conn->hequic[tempindex];
+
+ if(conn->ssl_config.verifyhost) {
+#ifdef USE_OPENSSL
+ X509 *server_cert;
+ server_cert = SSL_get_peer_certificate(conn->quic->ssl);
+ if(!server_cert) {
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ result = Curl_ossl_verifyhost(data, conn, server_cert);
+ X509_free(server_cert);
+ if(result)
+ return result;
+#elif defined(USE_GNUTLS)
+ result = Curl_gtls_verifyserver(data, conn->quic->gtls->session,
+ &conn->ssl_config, &data->set.ssl,
+ hostname, disp_hostname,
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
+ if(result)
+ return result;
+#elif defined(USE_WOLFSSL)
+ if(wolfSSL_check_domain_name(conn->quic->ssl, snihost) == SSL_FAILURE)
+ return CURLE_PEER_FAILED_VERIFICATION;
+#endif
+ infof(data, "Verified certificate just fine");
+ }
+ else
+ infof(data, "Skipped certificate verification");
+#ifdef USE_OPENSSL
+ if(data->set.ssl.certinfo)
+ /* asked to gather certificate info */
+ (void)Curl_ossl_certchain(data, conn->quic->ssl);
+#endif
+ return result;
+}
+
+/*
+ * There can be multiple connection attempts going on in parallel.
+ */
+CURLcode Curl_quic_is_connected(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex,
+ bool *done)
+{
+ CURLcode result;
+ struct quicsocket *qs = &conn->hequic[sockindex];
+ curl_socket_t sockfd = conn->tempsock[sockindex];
+
+ result = ng_process_ingress(data, sockfd, qs);
+ if(result)
+ goto error;
+
+ result = ng_flush_egress(data, sockfd, qs);
+ if(result)
+ goto error;
+
+ if(ngtcp2_conn_get_handshake_completed(qs->qconn)) {
+ result = ng_has_connected(data, conn, sockindex);
+ if(!result)
+ *done = TRUE;
+ }
+
+ return result;
+ error:
+ (void)qs_disconnect(qs);
+ return result;
+
+}
+
+static CURLcode ng_process_ingress(struct Curl_easy *data,
+ curl_socket_t sockfd,
+ struct quicsocket *qs)
+{
+ ssize_t recvd;
+ int rv;
+ uint8_t buf[65536];
+ size_t bufsize = sizeof(buf);
+ struct sockaddr_storage remote_addr;
+ socklen_t remote_addrlen;
+ ngtcp2_path path;
+ ngtcp2_tstamp ts = timestamp();
+ ngtcp2_pkt_info pi = { 0 };
+
+ for(;;) {
+ remote_addrlen = sizeof(remote_addr);
+ while((recvd = recvfrom(sockfd, (char *)buf, bufsize, 0,
+ (struct sockaddr *)&remote_addr,
+ &remote_addrlen)) == -1 &&
+ SOCKERRNO == EINTR)
+ ;
+ if(recvd == -1) {
+ if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK)
+ break;
+
+ failf(data, "ngtcp2: recvfrom() unexpectedly returned %zd", recvd);
+ return CURLE_RECV_ERROR;
+ }
+
+ ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr,
+ qs->local_addrlen);
+ ngtcp2_addr_init(&path.remote, (struct sockaddr *)&remote_addr,
+ remote_addrlen);
+
+ rv = ngtcp2_conn_read_pkt(qs->qconn, &path, &pi, buf, recvd, ts);
+ if(rv) {
+ 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 */
+ return CURLE_PEER_FAILED_VERIFICATION;
+ return CURLE_RECV_ERROR;
+ }
+ }
+
+ 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;
+ struct msghdr msg = {0};
+ ssize_t sent;
+#if defined(__linux__) && defined(UDP_SEGMENT)
+ uint8_t msg_ctrl[32];
+ struct cmsghdr *cm;
+#endif
+
+ *psent = 0;
+ msg_iov.iov_base = (uint8_t *)pkt;
+ msg_iov.iov_len = pktlen;
+ 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;
+ size_t sent;
+ ngtcp2_ssize outlen;
+ uint8_t *outpos = qs->pktbuf;
+ size_t max_udp_payload_size =
+ ngtcp2_conn_get_max_tx_udp_payload_size(qs->qconn);
+ size_t path_max_udp_payload_size =
+ ngtcp2_conn_get_path_max_tx_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;
+ nghttp3_ssize veccnt;
+ int fin;
+ nghttp3_vec vec[16];
+ 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(;;) {
+ veccnt = 0;
+ stream_id = -1;
+ fin = 0;
+
+ if(qs->h3conn && ngtcp2_conn_get_max_data_left(qs->qconn)) {
+ veccnt = nghttp3_conn_writev_stream(qs->h3conn, &stream_id, &fin, vec,
+ sizeof(vec) / sizeof(vec[0]));
+ 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, 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) {
+ switch(outlen) {
+ case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+ assert(ndatalen == -1);
+ nghttp3_conn_block_stream(qs->h3conn, stream_id);
+ continue;
+ case NGTCP2_ERR_STREAM_SHUT_WR:
+ assert(ndatalen == -1);
+ nghttp3_conn_shutdown_stream_write(qs->h3conn, stream_id);
+ continue;
+ case NGTCP2_ERR_WRITE_MORE:
+ assert(ndatalen >= 0);
+ rv = nghttp3_conn_add_write_offset(qs->h3conn, stream_id, ndatalen);
+ if(rv) {
+ failf(data, "nghttp3_conn_add_write_offset returned error: %s\n",
+ nghttp3_strerror(rv));
+ return CURLE_SEND_ERROR;
+ }
+ continue;
+ default:
+ 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;
+ }
+ }
+ else if(ndatalen >= 0) {
+ rv = nghttp3_conn_add_write_offset(qs->h3conn, stream_id, ndatalen);
+ if(rv) {
+ failf(data, "nghttp3_conn_add_write_offset returned error: %s\n",
+ nghttp3_strerror(rv));
+ return CURLE_SEND_ERROR;
+ }
+ }
+
+ outpos += outlen;
+
+ 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;
+ }
+ 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;
+ }
+ }
+
+ expiry = ngtcp2_conn_get_expiry(qs->qconn);
+ if(expiry != UINT64_MAX) {
+ if(expiry <= ts) {
+ timeout = 0;
+ }
+ else {
+ timeout = expiry - ts;
+ if(timeout % NGTCP2_MILLISECONDS) {
+ timeout += NGTCP2_MILLISECONDS;
+ }
+ }
+ Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC);
+ }
+
+ return CURLE_OK;
+}
+
+/*
+ * Called from transfer.c:done_sending when we stop HTTP/3 uploading.
+ */
+CURLcode Curl_quic_done_sending(struct Curl_easy *data)
+{
+ struct connectdata *conn = data->conn;
+ DEBUGASSERT(conn);
+ if(conn->handler == &Curl_handler_http3) {
+ /* only for HTTP/3 transfers */
+ struct HTTP *stream = data->req.p.http;
+ struct quicsocket *qs = conn->quic;
+ stream->upload_done = TRUE;
+ (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id);
+ }
+
+ 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.p.http;
+ Curl_dyn_free(&stream->overflow);
+ free(stream->h3out);
+ }
+}
+
+/*
+ * 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.p.http;
+ 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 6539f5fef3..41d88dbe9c 100644 --- a/libs/libcurl/src/vquic/ngtcp2.h +++ b/libs/libcurl/src/vquic/ngtcp2.h @@ -1,94 +1,93 @@ -#ifndef HEADER_CURL_VQUIC_NGTCP2_H -#define HEADER_CURL_VQUIC_NGTCP2_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * 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 - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * 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 - -#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> -#elif defined(USE_GNUTLS) -#include <gnutls/gnutls.h> -#elif defined(USE_WOLFSSL) -#include <wolfssl/options.h> -#include <wolfssl/ssl.h> -#include <wolfssl/quic.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; - ngtcp2_cid dcid; - ngtcp2_cid scid; - 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; -#elif defined(USE_GNUTLS) - gnutls_certificate_credentials_t cred; - gnutls_session_t ssl; -#elif defined(USE_WOLFSSL) - WOLFSSL_CTX *sslctx; - WOLFSSL *ssl; -#endif - 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; - int qlogfd; -}; - -#include "urldata.h" - -#endif - -#endif /* HEADER_CURL_VQUIC_NGTCP2_H */ +#ifndef HEADER_CURL_VQUIC_NGTCP2_H
+#define HEADER_CURL_VQUIC_NGTCP2_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * 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
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * 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
+
+#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>
+#elif defined(USE_WOLFSSL)
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/quic.h>
+#endif
+
+struct gtls_instance;
+
+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;
+ ngtcp2_cid dcid;
+ ngtcp2_cid scid;
+ 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;
+#elif defined(USE_GNUTLS)
+ struct gtls_instance *gtls;
+#elif defined(USE_WOLFSSL)
+ WOLFSSL_CTX *sslctx;
+ WOLFSSL *ssl;
+#endif
+ 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;
+ int qlogfd;
+};
+
+#include "urldata.h"
+
+#endif
+
+#endif /* HEADER_CURL_VQUIC_NGTCP2_H */
diff --git a/libs/libcurl/src/vquic/quiche.c b/libs/libcurl/src/vquic/quiche.c index a52a7e8e21..ea231d6379 100644 --- a/libs/libcurl/src/vquic/quiche.c +++ b/libs/libcurl/src/vquic/quiche.c @@ -1,895 +1,892 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * 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 - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * 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_QUICHE -#include <quiche.h> -#include <openssl/err.h> -#include <openssl/ssl.h> -#include "urldata.h" -#include "sendf.h" -#include "strdup.h" -#include "rand.h" -#include "quic.h" -#include "strcase.h" -#include "multiif.h" -#include "connect.h" -#include "strerror.h" -#include "vquic.h" -#include "transfer.h" -#include "h2h3.h" -#include "vtls/openssl.h" -#include "vtls/keylog.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -#define DEBUG_HTTP3 -/* #define DEBUG_QUICHE */ -#ifdef DEBUG_HTTP3 -#define H3BUGF(x) x -#else -#define H3BUGF(x) do { } while(0) -#endif - -#define QUIC_MAX_STREAMS (256*1024) -#define QUIC_MAX_DATA (1*1024*1024) -#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */ - -static CURLcode process_ingress(struct Curl_easy *data, - curl_socket_t sockfd, - struct quicsocket *qs); - -static CURLcode flush_egress(struct Curl_easy *data, curl_socket_t sockfd, - struct quicsocket *qs); - -static CURLcode http_request(struct Curl_easy *data, const void *mem, - size_t len); -static Curl_recv h3_stream_recv; -static Curl_send h3_stream_send; - -static int quiche_getsock(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t *socks) -{ - struct SingleRequest *k = &data->req; - int bitmap = GETSOCK_BLANK; - - socks[0] = conn->sock[FIRSTSOCKET]; - - /* in a HTTP/2 connection we can basically always get a frame so we should - always be ready for one */ - 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) - bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); - - return bitmap; -} - -static CURLcode qs_disconnect(struct Curl_easy *data, - struct quicsocket *qs) -{ - DEBUGASSERT(qs); - if(qs->conn) { - (void)quiche_conn_close(qs->conn, TRUE, 0, NULL, 0); - /* flushing the egress is not a failsafe way to deliver all the - outstanding packets, but we also don't want to get stuck here... */ - (void)flush_egress(data, qs->sockfd, qs); - quiche_conn_free(qs->conn); - qs->conn = NULL; - } - if(qs->h3config) - quiche_h3_config_free(qs->h3config); - if(qs->h3c) - quiche_h3_conn_free(qs->h3c); - if(qs->cfg) { - quiche_config_free(qs->cfg); - qs->cfg = NULL; - } - return CURLE_OK; -} - -static CURLcode quiche_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) -{ - struct quicsocket *qs = conn->quic; - (void)dead_connection; - return qs_disconnect(data, qs); -} - -void Curl_quic_disconnect(struct Curl_easy *data, - struct connectdata *conn, - int tempindex) -{ - if(conn->transport == TRNSPRT_QUIC) - qs_disconnect(data, &conn->hequic[tempindex]); -} - -static unsigned int quiche_conncheck(struct Curl_easy *data, - struct connectdata *conn, - unsigned int checks_to_perform) -{ - (void)data; - (void)conn; - (void)checks_to_perform; - return CONNRESULT_NONE; -} - -static CURLcode quiche_do(struct Curl_easy *data, bool *done) -{ - struct HTTP *stream = data->req.p.http; - stream->h3req = FALSE; /* not sent */ - return Curl_http(data, done); -} - -static const struct Curl_handler Curl_handler_http3 = { - "HTTPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - quiche_do, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - quiche_getsock, /* proto_getsock */ - quiche_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - quiche_getsock, /* perform_getsock */ - quiche_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - quiche_conncheck, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTPS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_SSL | PROTOPT_STREAM /* flags */ -}; - -#ifdef DEBUG_QUICHE -static void quiche_debug_log(const char *line, void *argp) -{ - (void)argp; - fprintf(stderr, "%s\n", line); -} -#endif - -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} - -static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data) -{ - SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); - - SSL_CTX_set_alpn_protos(ssl_ctx, - (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL, - sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); - - SSL_CTX_set_default_verify_paths(ssl_ctx); - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); - } - - { - struct connectdata *conn = data->conn; - if(conn->ssl_config.verifypeer) { - 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"); - } -#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; -} - -static int quic_init_ssl(struct quicsocket *qs, struct connectdata *conn) -{ - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = conn->host.name; - - DEBUGASSERT(!qs->ssl); - qs->ssl = SSL_new(qs->sslctx); - - SSL_set_app_data(qs->ssl, qs); - - /* set SNI */ - SSL_set_tlsext_host_name(qs->ssl, hostname); - return 0; -} - - -CURLcode Curl_quic_connect(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t sockfd, - int sockindex, - const struct sockaddr *addr, socklen_t addrlen) -{ - CURLcode result; - struct quicsocket *qs = &conn->hequic[sockindex]; - char ipbuf[40]; - int port; - int rv; - -#ifdef DEBUG_QUICHE - /* initialize debug log callback only once */ - static int debug_log_init = 0; - if(!debug_log_init) { - quiche_enable_debug_logging(quiche_debug_log, NULL); - debug_log_init = 1; - } -#endif - - (void)addr; - (void)addrlen; - - qs->sockfd = sockfd; - qs->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION); - if(!qs->cfg) { - failf(data, "can't create quiche config"); - return CURLE_FAILED_INIT; - } - - quiche_config_set_max_idle_timeout(qs->cfg, QUIC_IDLE_TIMEOUT); - quiche_config_set_initial_max_data(qs->cfg, QUIC_MAX_DATA); - quiche_config_set_initial_max_stream_data_bidi_local(qs->cfg, QUIC_MAX_DATA); - quiche_config_set_initial_max_stream_data_bidi_remote(qs->cfg, - QUIC_MAX_DATA); - quiche_config_set_initial_max_stream_data_uni(qs->cfg, QUIC_MAX_DATA); - quiche_config_set_initial_max_streams_bidi(qs->cfg, QUIC_MAX_STREAMS); - quiche_config_set_initial_max_streams_uni(qs->cfg, QUIC_MAX_STREAMS); - quiche_config_set_application_protos(qs->cfg, - (uint8_t *) - QUICHE_H3_APPLICATION_PROTOCOL, - sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - - 1); - - qs->sslctx = quic_ssl_ctx(data); - if(!qs->sslctx) - return CURLE_QUIC_CONNECT_ERROR; - - if(quic_init_ssl(qs, conn)) - return CURLE_QUIC_CONNECT_ERROR; - - result = Curl_rand(data, qs->scid, sizeof(qs->scid)); - if(result) - return result; - - qs->local_addrlen = sizeof(qs->local_addr); - rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr, - &qs->local_addrlen); - if(rv == -1) - return CURLE_QUIC_CONNECT_ERROR; - - qs->conn = quiche_conn_new_with_tls((const uint8_t *) qs->scid, - sizeof(qs->scid), NULL, 0, - (struct sockaddr *)&qs->local_addr, - qs->local_addrlen, addr, addrlen, - qs->cfg, qs->ssl, false); - if(!qs->conn) { - failf(data, "can't create quiche connection"); - return CURLE_OUT_OF_MEMORY; - } - - /* Known to not work on Windows */ -#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD) - { - int qfd; - (void)Curl_qlogdir(data, qs->scid, sizeof(qs->scid), &qfd); - if(qfd != -1) - quiche_conn_set_qlog_fd(qs->conn, qfd, - "qlog title", "curl qlog"); - } -#endif - - result = flush_egress(data, sockfd, qs); - if(result) - return result; - - /* extract the used address as a string */ - if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) { - char buffer[STRERROR_LEN]; - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - infof(data, "Connect socket %d over QUIC to %s:%ld", - sockfd, ipbuf, port); - - Curl_persistconninfo(data, conn, NULL, -1); - - /* for connection reuse purposes: */ - conn->ssl[FIRSTSOCKET].state = ssl_connection_complete; - - { - unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL; - unsigned alpn_len, offset = 0; - - /* Replace each ALPN length prefix by a comma. */ - while(offset < sizeof(alpn_protocols) - 1) { - alpn_len = alpn_protocols[offset]; - alpn_protocols[offset] = ','; - offset += 1 + alpn_len; - } - - infof(data, "Sent QUIC client Initial, ALPN: %s", - alpn_protocols + 1); - } - - return CURLE_OK; -} - -static CURLcode quiche_has_connected(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - int tempindex) -{ - CURLcode result; - struct quicsocket *qs = conn->quic = &conn->hequic[tempindex]; - - conn->recv[sockindex] = h3_stream_recv; - conn->send[sockindex] = h3_stream_send; - conn->handler = &Curl_handler_http3; - conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->httpversion = 30; - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - - if(conn->ssl_config.verifyhost) { - X509 *server_cert; - server_cert = SSL_get_peer_certificate(qs->ssl); - if(!server_cert) { - return CURLE_PEER_FAILED_VERIFICATION; - } - result = Curl_ossl_verifyhost(data, conn, server_cert); - X509_free(server_cert); - if(result) - return result; - infof(data, "Verified certificate just fine"); - } - else - infof(data, "Skipped certificate verification"); - - qs->h3config = quiche_h3_config_new(); - if(!qs->h3config) - return CURLE_OUT_OF_MEMORY; - - /* Create a new HTTP/3 connection on the QUIC connection. */ - qs->h3c = quiche_h3_conn_new_with_transport(qs->conn, qs->h3config); - if(!qs->h3c) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - if(conn->hequic[1-tempindex].cfg) { - qs = &conn->hequic[1-tempindex]; - quiche_config_free(qs->cfg); - quiche_conn_free(qs->conn); - qs->cfg = NULL; - qs->conn = NULL; - } - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, qs->ssl); - - return CURLE_OK; - fail: - quiche_h3_config_free(qs->h3config); - quiche_h3_conn_free(qs->h3c); - return result; -} - -/* - * This function gets polled to check if this QUIC connection has connected. - */ -CURLcode Curl_quic_is_connected(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool *done) -{ - CURLcode result; - struct quicsocket *qs = &conn->hequic[sockindex]; - curl_socket_t sockfd = conn->tempsock[sockindex]; - - result = process_ingress(data, sockfd, qs); - if(result) - goto error; - - result = flush_egress(data, sockfd, qs); - if(result) - goto error; - - if(quiche_conn_is_established(qs->conn)) { - *done = TRUE; - result = quiche_has_connected(data, conn, 0, sockindex); - DEBUGF(infof(data, "quiche established connection")); - } - - return result; - error: - qs_disconnect(data, qs); - return result; -} - -static CURLcode process_ingress(struct Curl_easy *data, int sockfd, - struct quicsocket *qs) -{ - ssize_t recvd; - uint8_t *buf = (uint8_t *)data->state.buffer; - size_t bufsize = data->set.buffer_size; - struct sockaddr_storage from; - socklen_t from_len; - quiche_recv_info recv_info; - - DEBUGASSERT(qs->conn); - - /* in case the timeout expired */ - quiche_conn_on_timeout(qs->conn); - - do { - from_len = sizeof(from); - - recvd = recvfrom(sockfd, buf, bufsize, 0, - (struct sockaddr *)&from, &from_len); - - if((recvd < 0) && ((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK))) - break; - - if(recvd < 0) { - failf(data, "quiche: recvfrom() unexpectedly returned %zd " - "(errno: %d, socket %d)", recvd, SOCKERRNO, sockfd); - return CURLE_RECV_ERROR; - } - - recv_info.from = (struct sockaddr *) &from; - recv_info.from_len = from_len; - recv_info.to = (struct sockaddr *) &qs->local_addr; - recv_info.to_len = qs->local_addrlen; - - recvd = quiche_conn_recv(qs->conn, buf, recvd, &recv_info); - if(recvd == QUICHE_ERR_DONE) - break; - - if(recvd < 0) { - if(QUICHE_ERR_TLS_FAIL == recvd) { - long verify_ok = SSL_get_verify_result(qs->ssl); - if(verify_ok != X509_V_OK) { - failf(data, "SSL certificate problem: %s", - X509_verify_cert_error_string(verify_ok)); - - return CURLE_PEER_FAILED_VERIFICATION; - } - } - - failf(data, "quiche_conn_recv() == %zd", recvd); - - return CURLE_RECV_ERROR; - } - } while(1); - - return CURLE_OK; -} - -/* - * flush_egress drains the buffers and sends off data. - * Calls failf() on errors. - */ -static CURLcode flush_egress(struct Curl_easy *data, int sockfd, - struct quicsocket *qs) -{ - ssize_t sent; - uint8_t out[1200]; - int64_t timeout_ns; - quiche_send_info send_info; - - do { - sent = quiche_conn_send(qs->conn, out, sizeof(out), &send_info); - if(sent == QUICHE_ERR_DONE) - break; - - if(sent < 0) { - failf(data, "quiche_conn_send returned %zd", sent); - return CURLE_SEND_ERROR; - } - - sent = send(sockfd, out, sent, 0); - if(sent < 0) { - failf(data, "send() returned %zd", sent); - return CURLE_SEND_ERROR; - } - } while(1); - - /* time until the next timeout event, as nanoseconds. */ - timeout_ns = quiche_conn_timeout_as_nanos(qs->conn); - if(timeout_ns) - /* expire uses milliseconds */ - Curl_expire(data, (timeout_ns + 999999) / 1000000, EXPIRE_QUIC); - - return CURLE_OK; -} - -struct h3h1header { - char *dest; - size_t destlen; /* left to use */ - size_t nlen; /* used */ -}; - -static int cb_each_header(uint8_t *name, size_t name_len, - uint8_t *value, size_t value_len, - void *argp) -{ - struct h3h1header *headers = (struct h3h1header *)argp; - size_t olen = 0; - - if((name_len == 7) && !strncmp(H2H3_PSEUDO_STATUS, (char *)name, 7)) { - msnprintf(headers->dest, - headers->destlen, "HTTP/3 %.*s\n", - (int) value_len, value); - } - else if(!headers->nlen) { - return CURLE_HTTP3; - } - else { - msnprintf(headers->dest, - headers->destlen, "%.*s: %.*s\n", - (int)name_len, name, (int) value_len, value); - } - olen = strlen(headers->dest); - headers->destlen -= olen; - headers->nlen += olen; - headers->dest += olen; - return 0; -} - -static ssize_t h3_stream_recv(struct Curl_easy *data, - int sockindex, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - ssize_t recvd = -1; - ssize_t rcode; - struct connectdata *conn = data->conn; - struct quicsocket *qs = conn->quic; - curl_socket_t sockfd = conn->sock[sockindex]; - quiche_h3_event *ev; - int rc; - struct h3h1header headers; - struct HTTP *stream = data->req.p.http; - headers.dest = buf; - headers.destlen = buffersize; - headers.nlen = 0; - - if(process_ingress(data, sockfd, qs)) { - infof(data, "h3_stream_recv returns on ingress"); - *curlcode = CURLE_RECV_ERROR; - return -1; - } - - if(qs->h3_recving) { - /* body receiving state */ - rcode = quiche_h3_recv_body(qs->h3c, qs->conn, stream->stream3_id, - (unsigned char *)buf, buffersize); - if(rcode <= 0) { - recvd = -1; - qs->h3_recving = FALSE; - /* fall through into the while loop below */ - } - else - recvd = rcode; - } - - while(recvd < 0) { - int64_t s = quiche_h3_conn_poll(qs->h3c, qs->conn, &ev); - if(s < 0) - /* nothing more to do */ - break; - - if(s != stream->stream3_id) { - /* another transfer, ignore for now */ - infof(data, "Got h3 for stream %u, expects %u", - s, stream->stream3_id); - continue; - } - - switch(quiche_h3_event_type(ev)) { - case QUICHE_H3_EVENT_HEADERS: - rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers); - if(rc) { - *curlcode = rc; - failf(data, "Error in HTTP/3 response header"); - break; - } - recvd = headers.nlen; - break; - case QUICHE_H3_EVENT_DATA: - if(!stream->firstbody) { - /* add a header-body separator CRLF */ - buf[0] = '\r'; - buf[1] = '\n'; - buf += 2; - buffersize -= 2; - stream->firstbody = TRUE; - recvd = 2; /* two bytes already */ - } - else - recvd = 0; - rcode = quiche_h3_recv_body(qs->h3c, qs->conn, s, (unsigned char *)buf, - buffersize); - if(rcode <= 0) { - recvd = -1; - break; - } - qs->h3_recving = TRUE; - recvd += rcode; - break; - - case QUICHE_H3_EVENT_RESET: - streamclose(conn, "Stream reset"); - *curlcode = CURLE_PARTIAL_FILE; - return -1; - - case QUICHE_H3_EVENT_FINISHED: - streamclose(conn, "End of stream"); - recvd = 0; /* end of stream */ - break; - default: - break; - } - - quiche_h3_event_free(ev); - } - if(flush_egress(data, sockfd, qs)) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - - *curlcode = (-1 == recvd)? CURLE_AGAIN : CURLE_OK; - if(recvd >= 0) - /* Get this called again to drain the event queue */ - Curl_expire(data, 0, EXPIRE_QUIC); - - data->state.drain = (recvd >= 0) ? 1 : 0; - return recvd; -} - -static ssize_t h3_stream_send(struct Curl_easy *data, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - ssize_t sent; - struct connectdata *conn = data->conn; - struct quicsocket *qs = conn->quic; - curl_socket_t sockfd = conn->sock[sockindex]; - struct HTTP *stream = data->req.p.http; - - if(!stream->h3req) { - CURLcode result = http_request(data, mem, len); - if(result) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - sent = len; - } - else { - sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id, - (uint8_t *)mem, len, FALSE); - if(sent == QUICHE_H3_ERR_DONE) { - sent = 0; - } - else if(sent < 0) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - } - - if(flush_egress(data, sockfd, qs)) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - - *curlcode = CURLE_OK; - return sent; -} - -/* - * Store quiche version info in this buffer. - */ -void Curl_quic_ver(char *p, size_t len) -{ - (void)msnprintf(p, len, "quiche/%s", quiche_version()); -} - -/* Index where :authority header field will appear in request header - field list. */ -#define AUTHORITY_DST_IDX 3 - -static CURLcode http_request(struct Curl_easy *data, const void *mem, - size_t len) -{ - struct connectdata *conn = data->conn; - struct HTTP *stream = data->req.p.http; - size_t nheader; - int64_t stream3_id; - quiche_h3_header *nva = NULL; - struct quicsocket *qs = conn->quic; - CURLcode result = CURLE_OK; - struct h2h3req *hreq = NULL; - - stream->h3req = TRUE; /* senf off! */ - - result = Curl_pseudo_headers(data, mem, len, &hreq); - if(result) - goto fail; - nheader = hreq->entries; - - nva = malloc(sizeof(quiche_h3_header) * nheader); - if(!nva) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - else { - unsigned int i; - for(i = 0; i < nheader; i++) { - nva[i].name = (unsigned char *)hreq->header[i].name; - nva[i].name_len = hreq->header[i].namelen; - nva[i].value = (unsigned char *)hreq->header[i].value; - nva[i].value_len = hreq->header[i].valuelen; - } - } - - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - case HTTPREQ_PUT: - if(data->state.infilesize != -1) - stream->upload_left = data->state.infilesize; - else - /* data sending without specifying the data amount up front */ - stream->upload_left = -1; /* unknown, but not zero */ - - stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader, - stream->upload_left ? FALSE: TRUE); - if((stream3_id >= 0) && data->set.postfields) { - ssize_t sent = quiche_h3_send_body(qs->h3c, qs->conn, stream3_id, - (uint8_t *)data->set.postfields, - stream->upload_left, TRUE); - if(sent <= 0) { - failf(data, "quiche_h3_send_body failed"); - result = CURLE_SEND_ERROR; - } - stream->upload_left = 0; /* nothing left to send */ - } - break; - default: - stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader, - TRUE); - break; - } - - Curl_safefree(nva); - - if(stream3_id < 0) { - H3BUGF(infof(data, "quiche_h3_send_request returned %d", - stream3_id)); - result = CURLE_SEND_ERROR; - goto fail; - } - - infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)", - stream3_id, (void *)data); - stream->stream3_id = stream3_id; - - Curl_pseudo_free(hreq); - return CURLE_OK; - -fail: - free(nva); - Curl_pseudo_free(hreq); - return result; -} - -/* - * Called from transfer.c:done_sending when we stop HTTP/3 uploading. - */ -CURLcode Curl_quic_done_sending(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - DEBUGASSERT(conn); - if(conn->handler == &Curl_handler_http3) { - /* only for HTTP/3 transfers */ - ssize_t sent; - struct HTTP *stream = data->req.p.http; - struct quicsocket *qs = conn->quic; - stream->upload_done = TRUE; - sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id, - NULL, 0, TRUE); - if(sent < 0) - return CURLE_SEND_ERROR; - } - - 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)data; - (void)premature; -} - -/* - * 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) -{ - (void)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 +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * 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
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * 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_QUICHE
+#include <quiche.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include "urldata.h"
+#include "sendf.h"
+#include "strdup.h"
+#include "rand.h"
+#include "quic.h"
+#include "strcase.h"
+#include "multiif.h"
+#include "connect.h"
+#include "strerror.h"
+#include "vquic.h"
+#include "transfer.h"
+#include "h2h3.h"
+#include "vtls/openssl.h"
+#include "vtls/keylog.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#define DEBUG_HTTP3
+/* #define DEBUG_QUICHE */
+#ifdef DEBUG_HTTP3
+#define H3BUGF(x) x
+#else
+#define H3BUGF(x) do { } while(0)
+#endif
+
+#define QUIC_MAX_STREAMS (256*1024)
+#define QUIC_MAX_DATA (1*1024*1024)
+#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */
+
+static CURLcode process_ingress(struct Curl_easy *data,
+ curl_socket_t sockfd,
+ struct quicsocket *qs);
+
+static CURLcode flush_egress(struct Curl_easy *data, curl_socket_t sockfd,
+ struct quicsocket *qs);
+
+static CURLcode http_request(struct Curl_easy *data, const void *mem,
+ size_t len);
+static Curl_recv h3_stream_recv;
+static Curl_send h3_stream_send;
+
+static int quiche_getsock(struct Curl_easy *data,
+ struct connectdata *conn, curl_socket_t *socks)
+{
+ struct SingleRequest *k = &data->req;
+ int bitmap = GETSOCK_BLANK;
+
+ socks[0] = conn->sock[FIRSTSOCKET];
+
+ /* in an HTTP/2 connection we can basically always get a frame so we should
+ always be ready for one */
+ 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)
+ bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
+
+ return bitmap;
+}
+
+static CURLcode qs_disconnect(struct Curl_easy *data,
+ struct quicsocket *qs)
+{
+ DEBUGASSERT(qs);
+ if(qs->conn) {
+ (void)quiche_conn_close(qs->conn, TRUE, 0, NULL, 0);
+ /* flushing the egress is not a failsafe way to deliver all the
+ outstanding packets, but we also don't want to get stuck here... */
+ (void)flush_egress(data, qs->sockfd, qs);
+ quiche_conn_free(qs->conn);
+ qs->conn = NULL;
+ }
+ if(qs->h3config)
+ quiche_h3_config_free(qs->h3config);
+ if(qs->h3c)
+ quiche_h3_conn_free(qs->h3c);
+ if(qs->cfg) {
+ quiche_config_free(qs->cfg);
+ qs->cfg = NULL;
+ }
+ return CURLE_OK;
+}
+
+static CURLcode quiche_disconnect(struct Curl_easy *data,
+ struct connectdata *conn,
+ bool dead_connection)
+{
+ struct quicsocket *qs = conn->quic;
+ (void)dead_connection;
+ return qs_disconnect(data, qs);
+}
+
+void Curl_quic_disconnect(struct Curl_easy *data,
+ struct connectdata *conn,
+ int tempindex)
+{
+ if(conn->transport == TRNSPRT_QUIC)
+ qs_disconnect(data, &conn->hequic[tempindex]);
+}
+
+static unsigned int quiche_conncheck(struct Curl_easy *data,
+ struct connectdata *conn,
+ unsigned int checks_to_perform)
+{
+ (void)data;
+ (void)conn;
+ (void)checks_to_perform;
+ return CONNRESULT_NONE;
+}
+
+static CURLcode quiche_do(struct Curl_easy *data, bool *done)
+{
+ struct HTTP *stream = data->req.p.http;
+ stream->h3req = FALSE; /* not sent */
+ return Curl_http(data, done);
+}
+
+static const struct Curl_handler Curl_handler_http3 = {
+ "HTTPS", /* scheme */
+ ZERO_NULL, /* setup_connection */
+ quiche_do, /* do_it */
+ Curl_http_done, /* done */
+ ZERO_NULL, /* do_more */
+ ZERO_NULL, /* connect_it */
+ ZERO_NULL, /* connecting */
+ ZERO_NULL, /* doing */
+ quiche_getsock, /* proto_getsock */
+ quiche_getsock, /* doing_getsock */
+ ZERO_NULL, /* domore_getsock */
+ quiche_getsock, /* perform_getsock */
+ quiche_disconnect, /* disconnect */
+ ZERO_NULL, /* readwrite */
+ quiche_conncheck, /* connection_check */
+ ZERO_NULL, /* attach connection */
+ PORT_HTTP, /* defport */
+ CURLPROTO_HTTPS, /* protocol */
+ CURLPROTO_HTTP, /* family */
+ PROTOPT_SSL | PROTOPT_STREAM /* flags */
+};
+
+#ifdef DEBUG_QUICHE
+static void quiche_debug_log(const char *line, void *argp)
+{
+ (void)argp;
+ fprintf(stderr, "%s\n", line);
+}
+#endif
+
+static void keylog_callback(const SSL *ssl, const char *line)
+{
+ (void)ssl;
+ Curl_tls_keylog_write_line(line);
+}
+
+static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
+{
+ SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
+
+ SSL_CTX_set_alpn_protos(ssl_ctx,
+ (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL,
+ sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1);
+
+ SSL_CTX_set_default_verify_paths(ssl_ctx);
+
+ /* Open the file if a TLS or QUIC backend has not done this before. */
+ Curl_tls_keylog_open();
+ if(Curl_tls_keylog_enabled()) {
+ SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+ }
+
+ {
+ struct connectdata *conn = data->conn;
+ if(conn->ssl_config.verifypeer) {
+ 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");
+ }
+#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;
+}
+
+static int quic_init_ssl(struct quicsocket *qs, struct connectdata *conn)
+{
+ /* this will need some attention when HTTPS proxy over QUIC get fixed */
+ const char * const hostname = conn->host.name;
+
+ DEBUGASSERT(!qs->ssl);
+ qs->ssl = SSL_new(qs->sslctx);
+
+ SSL_set_app_data(qs->ssl, qs);
+
+ /* set SNI */
+ SSL_set_tlsext_host_name(qs->ssl, hostname);
+ return 0;
+}
+
+
+CURLcode Curl_quic_connect(struct Curl_easy *data,
+ struct connectdata *conn, curl_socket_t sockfd,
+ int sockindex,
+ const struct sockaddr *addr, socklen_t addrlen)
+{
+ CURLcode result;
+ struct quicsocket *qs = &conn->hequic[sockindex];
+ char ipbuf[40];
+ int port;
+ int rv;
+
+#ifdef DEBUG_QUICHE
+ /* initialize debug log callback only once */
+ static int debug_log_init = 0;
+ if(!debug_log_init) {
+ quiche_enable_debug_logging(quiche_debug_log, NULL);
+ debug_log_init = 1;
+ }
+#endif
+
+ (void)addr;
+ (void)addrlen;
+
+ qs->sockfd = sockfd;
+ qs->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
+ if(!qs->cfg) {
+ failf(data, "can't create quiche config");
+ return CURLE_FAILED_INIT;
+ }
+
+ quiche_config_set_max_idle_timeout(qs->cfg, QUIC_IDLE_TIMEOUT);
+ quiche_config_set_initial_max_data(qs->cfg, QUIC_MAX_DATA);
+ quiche_config_set_initial_max_stream_data_bidi_local(qs->cfg, QUIC_MAX_DATA);
+ quiche_config_set_initial_max_stream_data_bidi_remote(qs->cfg,
+ QUIC_MAX_DATA);
+ quiche_config_set_initial_max_stream_data_uni(qs->cfg, QUIC_MAX_DATA);
+ quiche_config_set_initial_max_streams_bidi(qs->cfg, QUIC_MAX_STREAMS);
+ quiche_config_set_initial_max_streams_uni(qs->cfg, QUIC_MAX_STREAMS);
+ quiche_config_set_application_protos(qs->cfg,
+ (uint8_t *)
+ QUICHE_H3_APPLICATION_PROTOCOL,
+ sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
+ - 1);
+
+ qs->sslctx = quic_ssl_ctx(data);
+ if(!qs->sslctx)
+ return CURLE_QUIC_CONNECT_ERROR;
+
+ if(quic_init_ssl(qs, conn))
+ return CURLE_QUIC_CONNECT_ERROR;
+
+ result = Curl_rand(data, qs->scid, sizeof(qs->scid));
+ if(result)
+ return result;
+
+ qs->local_addrlen = sizeof(qs->local_addr);
+ rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr,
+ &qs->local_addrlen);
+ if(rv == -1)
+ return CURLE_QUIC_CONNECT_ERROR;
+
+ qs->conn = quiche_conn_new_with_tls((const uint8_t *) qs->scid,
+ sizeof(qs->scid), NULL, 0,
+ (struct sockaddr *)&qs->local_addr,
+ qs->local_addrlen, addr, addrlen,
+ qs->cfg, qs->ssl, false);
+ if(!qs->conn) {
+ failf(data, "can't create quiche connection");
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ /* Known to not work on Windows */
+#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD)
+ {
+ int qfd;
+ (void)Curl_qlogdir(data, qs->scid, sizeof(qs->scid), &qfd);
+ if(qfd != -1)
+ quiche_conn_set_qlog_fd(qs->conn, qfd,
+ "qlog title", "curl qlog");
+ }
+#endif
+
+ result = flush_egress(data, sockfd, qs);
+ if(result)
+ return result;
+
+ /* extract the used address as a string */
+ if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) {
+ char buffer[STRERROR_LEN];
+ failf(data, "ssrem inet_ntop() failed with errno %d: %s",
+ SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+
+ infof(data, "Connect socket %d over QUIC to %s:%ld",
+ sockfd, ipbuf, port);
+
+ Curl_persistconninfo(data, conn, NULL, -1);
+
+ {
+ unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL;
+ unsigned alpn_len, offset = 0;
+
+ /* Replace each ALPN length prefix by a comma. */
+ while(offset < sizeof(alpn_protocols) - 1) {
+ alpn_len = alpn_protocols[offset];
+ alpn_protocols[offset] = ',';
+ offset += 1 + alpn_len;
+ }
+
+ infof(data, "Sent QUIC client Initial, ALPN: %s",
+ alpn_protocols + 1);
+ }
+
+ return CURLE_OK;
+}
+
+static CURLcode quiche_has_connected(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex,
+ int tempindex)
+{
+ CURLcode result;
+ struct quicsocket *qs = conn->quic = &conn->hequic[tempindex];
+
+ conn->recv[sockindex] = h3_stream_recv;
+ conn->send[sockindex] = h3_stream_send;
+ conn->handler = &Curl_handler_http3;
+ conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+ conn->httpversion = 30;
+ conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+
+ if(conn->ssl_config.verifyhost) {
+ X509 *server_cert;
+ server_cert = SSL_get_peer_certificate(qs->ssl);
+ if(!server_cert) {
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ result = Curl_ossl_verifyhost(data, conn, server_cert);
+ X509_free(server_cert);
+ if(result)
+ return result;
+ infof(data, "Verified certificate just fine");
+ }
+ else
+ infof(data, "Skipped certificate verification");
+
+ qs->h3config = quiche_h3_config_new();
+ if(!qs->h3config)
+ return CURLE_OUT_OF_MEMORY;
+
+ /* Create a new HTTP/3 connection on the QUIC connection. */
+ qs->h3c = quiche_h3_conn_new_with_transport(qs->conn, qs->h3config);
+ if(!qs->h3c) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ if(conn->hequic[1-tempindex].cfg) {
+ qs = &conn->hequic[1-tempindex];
+ quiche_config_free(qs->cfg);
+ quiche_conn_free(qs->conn);
+ qs->cfg = NULL;
+ qs->conn = NULL;
+ }
+ if(data->set.ssl.certinfo)
+ /* asked to gather certificate info */
+ (void)Curl_ossl_certchain(data, qs->ssl);
+
+ return CURLE_OK;
+ fail:
+ quiche_h3_config_free(qs->h3config);
+ quiche_h3_conn_free(qs->h3c);
+ return result;
+}
+
+/*
+ * This function gets polled to check if this QUIC connection has connected.
+ */
+CURLcode Curl_quic_is_connected(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex,
+ bool *done)
+{
+ CURLcode result;
+ struct quicsocket *qs = &conn->hequic[sockindex];
+ curl_socket_t sockfd = conn->tempsock[sockindex];
+
+ result = process_ingress(data, sockfd, qs);
+ if(result)
+ goto error;
+
+ result = flush_egress(data, sockfd, qs);
+ if(result)
+ goto error;
+
+ if(quiche_conn_is_established(qs->conn)) {
+ *done = TRUE;
+ result = quiche_has_connected(data, conn, 0, sockindex);
+ DEBUGF(infof(data, "quiche established connection"));
+ }
+
+ return result;
+ error:
+ qs_disconnect(data, qs);
+ return result;
+}
+
+static CURLcode process_ingress(struct Curl_easy *data, int sockfd,
+ struct quicsocket *qs)
+{
+ ssize_t recvd;
+ uint8_t *buf = (uint8_t *)data->state.buffer;
+ size_t bufsize = data->set.buffer_size;
+ struct sockaddr_storage from;
+ socklen_t from_len;
+ quiche_recv_info recv_info;
+
+ DEBUGASSERT(qs->conn);
+
+ /* in case the timeout expired */
+ quiche_conn_on_timeout(qs->conn);
+
+ do {
+ from_len = sizeof(from);
+
+ recvd = recvfrom(sockfd, buf, bufsize, 0,
+ (struct sockaddr *)&from, &from_len);
+
+ if((recvd < 0) && ((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK)))
+ break;
+
+ if(recvd < 0) {
+ failf(data, "quiche: recvfrom() unexpectedly returned %zd "
+ "(errno: %d, socket %d)", recvd, SOCKERRNO, sockfd);
+ return CURLE_RECV_ERROR;
+ }
+
+ recv_info.from = (struct sockaddr *) &from;
+ recv_info.from_len = from_len;
+ recv_info.to = (struct sockaddr *) &qs->local_addr;
+ recv_info.to_len = qs->local_addrlen;
+
+ recvd = quiche_conn_recv(qs->conn, buf, recvd, &recv_info);
+ if(recvd == QUICHE_ERR_DONE)
+ break;
+
+ if(recvd < 0) {
+ if(QUICHE_ERR_TLS_FAIL == recvd) {
+ long verify_ok = SSL_get_verify_result(qs->ssl);
+ if(verify_ok != X509_V_OK) {
+ failf(data, "SSL certificate problem: %s",
+ X509_verify_cert_error_string(verify_ok));
+
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ }
+
+ failf(data, "quiche_conn_recv() == %zd", recvd);
+
+ return CURLE_RECV_ERROR;
+ }
+ } while(1);
+
+ return CURLE_OK;
+}
+
+/*
+ * flush_egress drains the buffers and sends off data.
+ * Calls failf() on errors.
+ */
+static CURLcode flush_egress(struct Curl_easy *data, int sockfd,
+ struct quicsocket *qs)
+{
+ ssize_t sent;
+ uint8_t out[1200];
+ int64_t timeout_ns;
+ quiche_send_info send_info;
+
+ do {
+ sent = quiche_conn_send(qs->conn, out, sizeof(out), &send_info);
+ if(sent == QUICHE_ERR_DONE)
+ break;
+
+ if(sent < 0) {
+ failf(data, "quiche_conn_send returned %zd", sent);
+ return CURLE_SEND_ERROR;
+ }
+
+ sent = send(sockfd, out, sent, 0);
+ if(sent < 0) {
+ failf(data, "send() returned %zd", sent);
+ return CURLE_SEND_ERROR;
+ }
+ } while(1);
+
+ /* time until the next timeout event, as nanoseconds. */
+ timeout_ns = quiche_conn_timeout_as_nanos(qs->conn);
+ if(timeout_ns)
+ /* expire uses milliseconds */
+ Curl_expire(data, (timeout_ns + 999999) / 1000000, EXPIRE_QUIC);
+
+ return CURLE_OK;
+}
+
+struct h3h1header {
+ char *dest;
+ size_t destlen; /* left to use */
+ size_t nlen; /* used */
+};
+
+static int cb_each_header(uint8_t *name, size_t name_len,
+ uint8_t *value, size_t value_len,
+ void *argp)
+{
+ struct h3h1header *headers = (struct h3h1header *)argp;
+ size_t olen = 0;
+
+ if((name_len == 7) && !strncmp(H2H3_PSEUDO_STATUS, (char *)name, 7)) {
+ msnprintf(headers->dest,
+ headers->destlen, "HTTP/3 %.*s\n",
+ (int) value_len, value);
+ }
+ else if(!headers->nlen) {
+ return CURLE_HTTP3;
+ }
+ else {
+ msnprintf(headers->dest,
+ headers->destlen, "%.*s: %.*s\n",
+ (int)name_len, name, (int) value_len, value);
+ }
+ olen = strlen(headers->dest);
+ headers->destlen -= olen;
+ headers->nlen += olen;
+ headers->dest += olen;
+ return 0;
+}
+
+static ssize_t h3_stream_recv(struct Curl_easy *data,
+ int sockindex,
+ char *buf,
+ size_t buffersize,
+ CURLcode *curlcode)
+{
+ ssize_t recvd = -1;
+ ssize_t rcode;
+ struct connectdata *conn = data->conn;
+ struct quicsocket *qs = conn->quic;
+ curl_socket_t sockfd = conn->sock[sockindex];
+ quiche_h3_event *ev;
+ int rc;
+ struct h3h1header headers;
+ struct HTTP *stream = data->req.p.http;
+ headers.dest = buf;
+ headers.destlen = buffersize;
+ headers.nlen = 0;
+
+ if(process_ingress(data, sockfd, qs)) {
+ infof(data, "h3_stream_recv returns on ingress");
+ *curlcode = CURLE_RECV_ERROR;
+ return -1;
+ }
+
+ if(qs->h3_recving) {
+ /* body receiving state */
+ rcode = quiche_h3_recv_body(qs->h3c, qs->conn, stream->stream3_id,
+ (unsigned char *)buf, buffersize);
+ if(rcode <= 0) {
+ recvd = -1;
+ qs->h3_recving = FALSE;
+ /* fall through into the while loop below */
+ }
+ else
+ recvd = rcode;
+ }
+
+ while(recvd < 0) {
+ int64_t s = quiche_h3_conn_poll(qs->h3c, qs->conn, &ev);
+ if(s < 0)
+ /* nothing more to do */
+ break;
+
+ if(s != stream->stream3_id) {
+ /* another transfer, ignore for now */
+ infof(data, "Got h3 for stream %u, expects %u",
+ s, stream->stream3_id);
+ continue;
+ }
+
+ switch(quiche_h3_event_type(ev)) {
+ case QUICHE_H3_EVENT_HEADERS:
+ rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers);
+ if(rc) {
+ *curlcode = rc;
+ failf(data, "Error in HTTP/3 response header");
+ break;
+ }
+ recvd = headers.nlen;
+ break;
+ case QUICHE_H3_EVENT_DATA:
+ if(!stream->firstbody) {
+ /* add a header-body separator CRLF */
+ buf[0] = '\r';
+ buf[1] = '\n';
+ buf += 2;
+ buffersize -= 2;
+ stream->firstbody = TRUE;
+ recvd = 2; /* two bytes already */
+ }
+ else
+ recvd = 0;
+ rcode = quiche_h3_recv_body(qs->h3c, qs->conn, s, (unsigned char *)buf,
+ buffersize);
+ if(rcode <= 0) {
+ recvd = -1;
+ break;
+ }
+ qs->h3_recving = TRUE;
+ recvd += rcode;
+ break;
+
+ case QUICHE_H3_EVENT_RESET:
+ streamclose(conn, "Stream reset");
+ *curlcode = CURLE_PARTIAL_FILE;
+ return -1;
+
+ case QUICHE_H3_EVENT_FINISHED:
+ streamclose(conn, "End of stream");
+ recvd = 0; /* end of stream */
+ break;
+ default:
+ break;
+ }
+
+ quiche_h3_event_free(ev);
+ }
+ if(flush_egress(data, sockfd, qs)) {
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+ }
+
+ *curlcode = (-1 == recvd)? CURLE_AGAIN : CURLE_OK;
+ if(recvd >= 0)
+ /* Get this called again to drain the event queue */
+ Curl_expire(data, 0, EXPIRE_QUIC);
+
+ data->state.drain = (recvd >= 0) ? 1 : 0;
+ return recvd;
+}
+
+static ssize_t h3_stream_send(struct Curl_easy *data,
+ int sockindex,
+ const void *mem,
+ size_t len,
+ CURLcode *curlcode)
+{
+ ssize_t sent;
+ struct connectdata *conn = data->conn;
+ struct quicsocket *qs = conn->quic;
+ curl_socket_t sockfd = conn->sock[sockindex];
+ struct HTTP *stream = data->req.p.http;
+
+ if(!stream->h3req) {
+ CURLcode result = http_request(data, mem, len);
+ if(result) {
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+ }
+ sent = len;
+ }
+ else {
+ sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id,
+ (uint8_t *)mem, len, FALSE);
+ if(sent == QUICHE_H3_ERR_DONE) {
+ sent = 0;
+ }
+ else if(sent < 0) {
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+ }
+ }
+
+ if(flush_egress(data, sockfd, qs)) {
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+ }
+
+ *curlcode = CURLE_OK;
+ return sent;
+}
+
+/*
+ * Store quiche version info in this buffer.
+ */
+void Curl_quic_ver(char *p, size_t len)
+{
+ (void)msnprintf(p, len, "quiche/%s", quiche_version());
+}
+
+/* Index where :authority header field will appear in request header
+ field list. */
+#define AUTHORITY_DST_IDX 3
+
+static CURLcode http_request(struct Curl_easy *data, const void *mem,
+ size_t len)
+{
+ struct connectdata *conn = data->conn;
+ struct HTTP *stream = data->req.p.http;
+ size_t nheader;
+ int64_t stream3_id;
+ quiche_h3_header *nva = NULL;
+ struct quicsocket *qs = conn->quic;
+ CURLcode result = CURLE_OK;
+ struct h2h3req *hreq = NULL;
+
+ stream->h3req = TRUE; /* senf off! */
+
+ result = Curl_pseudo_headers(data, mem, len, &hreq);
+ if(result)
+ goto fail;
+ nheader = hreq->entries;
+
+ nva = malloc(sizeof(quiche_h3_header) * nheader);
+ if(!nva) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ else {
+ unsigned int i;
+ for(i = 0; i < nheader; i++) {
+ nva[i].name = (unsigned char *)hreq->header[i].name;
+ nva[i].name_len = hreq->header[i].namelen;
+ nva[i].value = (unsigned char *)hreq->header[i].value;
+ nva[i].value_len = hreq->header[i].valuelen;
+ }
+ }
+
+ switch(data->state.httpreq) {
+ case HTTPREQ_POST:
+ case HTTPREQ_POST_FORM:
+ case HTTPREQ_POST_MIME:
+ case HTTPREQ_PUT:
+ if(data->state.infilesize != -1)
+ stream->upload_left = data->state.infilesize;
+ else
+ /* data sending without specifying the data amount up front */
+ stream->upload_left = -1; /* unknown, but not zero */
+
+ stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader,
+ stream->upload_left ? FALSE: TRUE);
+ if((stream3_id >= 0) && data->set.postfields) {
+ ssize_t sent = quiche_h3_send_body(qs->h3c, qs->conn, stream3_id,
+ (uint8_t *)data->set.postfields,
+ stream->upload_left, TRUE);
+ if(sent <= 0) {
+ failf(data, "quiche_h3_send_body failed");
+ result = CURLE_SEND_ERROR;
+ }
+ stream->upload_left = 0; /* nothing left to send */
+ }
+ break;
+ default:
+ stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader,
+ TRUE);
+ break;
+ }
+
+ Curl_safefree(nva);
+
+ if(stream3_id < 0) {
+ H3BUGF(infof(data, "quiche_h3_send_request returned %d",
+ stream3_id));
+ result = CURLE_SEND_ERROR;
+ goto fail;
+ }
+
+ infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)",
+ stream3_id, (void *)data);
+ stream->stream3_id = stream3_id;
+
+ Curl_pseudo_free(hreq);
+ return CURLE_OK;
+
+fail:
+ free(nva);
+ Curl_pseudo_free(hreq);
+ return result;
+}
+
+/*
+ * Called from transfer.c:done_sending when we stop HTTP/3 uploading.
+ */
+CURLcode Curl_quic_done_sending(struct Curl_easy *data)
+{
+ struct connectdata *conn = data->conn;
+ DEBUGASSERT(conn);
+ if(conn->handler == &Curl_handler_http3) {
+ /* only for HTTP/3 transfers */
+ ssize_t sent;
+ struct HTTP *stream = data->req.p.http;
+ struct quicsocket *qs = conn->quic;
+ stream->upload_done = TRUE;
+ sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id,
+ NULL, 0, TRUE);
+ if(sent < 0)
+ return CURLE_SEND_ERROR;
+ }
+
+ 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)data;
+ (void)premature;
+}
+
+/*
+ * 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)
+{
+ (void)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 2da65f5f48..79eb9bcff5 100644 --- a/libs/libcurl/src/vquic/quiche.h +++ b/libs/libcurl/src/vquic/quiche.h @@ -1,58 +1,58 @@ -#ifndef HEADER_CURL_VQUIC_QUICHE_H -#define HEADER_CURL_VQUIC_QUICHE_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * 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 - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * 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_QUICHE - -#include <quiche.h> -#include <openssl/ssl.h> - -struct quic_handshake { - char *buf; /* pointer to the buffer */ - size_t alloclen; /* size of allocation */ - size_t len; /* size of content in buffer */ - size_t nread; /* how many bytes have been read */ -}; - -struct quicsocket { - quiche_config *cfg; - quiche_conn *conn; - quiche_h3_conn *h3c; - quiche_h3_config *h3config; - uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; - curl_socket_t sockfd; - uint32_t version; - SSL_CTX *sslctx; - SSL *ssl; - bool h3_recving; /* TRUE when in h3-body-reading state */ - struct sockaddr_storage local_addr; - socklen_t local_addrlen; -}; - -#endif - -#endif /* HEADER_CURL_VQUIC_QUICHE_H */ +#ifndef HEADER_CURL_VQUIC_QUICHE_H
+#define HEADER_CURL_VQUIC_QUICHE_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * 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
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * 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_QUICHE
+
+#include <quiche.h>
+#include <openssl/ssl.h>
+
+struct quic_handshake {
+ char *buf; /* pointer to the buffer */
+ size_t alloclen; /* size of allocation */
+ size_t len; /* size of content in buffer */
+ size_t nread; /* how many bytes have been read */
+};
+
+struct quicsocket {
+ quiche_config *cfg;
+ quiche_conn *conn;
+ quiche_h3_conn *h3c;
+ quiche_h3_config *h3config;
+ uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
+ curl_socket_t sockfd;
+ uint32_t version;
+ SSL_CTX *sslctx;
+ SSL *ssl;
+ bool h3_recving; /* TRUE when in h3-body-reading state */
+ struct sockaddr_storage local_addr;
+ socklen_t local_addrlen;
+};
+
+#endif
+
+#endif /* HEADER_CURL_VQUIC_QUICHE_H */
diff --git a/libs/libcurl/src/vquic/vquic.c b/libs/libcurl/src/vquic/vquic.c index e52a4f301d..0bb7a5253e 100644 --- a/libs/libcurl/src/vquic/vquic.c +++ b/libs/libcurl/src/vquic/vquic.c @@ -1,87 +1,87 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * 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 - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * 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 ENABLE_QUIC - -#ifdef HAVE_FCNTL_H -#include <fcntl.h> -#endif -#include "urldata.h" -#include "dynbuf.h" -#include "curl_printf.h" -#include "vquic.h" - -#ifdef O_BINARY -#define QLOGMODE O_WRONLY|O_CREAT|O_BINARY -#else -#define QLOGMODE O_WRONLY|O_CREAT -#endif - -/* - * If the QLOGDIR environment variable is set, open and return a file - * descriptor to write the log to. - * - * This function returns error if something failed outside of failing to - * create the file. Open file success is deemed by seeing if the returned fd - * is != -1. - */ -CURLcode Curl_qlogdir(struct Curl_easy *data, - unsigned char *scid, - size_t scidlen, - int *qlogfdp) -{ - const char *qlog_dir = getenv("QLOGDIR"); - *qlogfdp = -1; - if(qlog_dir) { - struct dynbuf fname; - CURLcode result; - unsigned int i; - Curl_dyn_init(&fname, DYN_QLOG_NAME); - result = Curl_dyn_add(&fname, qlog_dir); - if(!result) - result = Curl_dyn_add(&fname, "/"); - for(i = 0; (i < scidlen) && !result; i++) { - char hex[3]; - msnprintf(hex, 3, "%02x", scid[i]); - result = Curl_dyn_add(&fname, hex); - } - if(!result) - result = Curl_dyn_add(&fname, ".sqlog"); - - if(!result) { - int qlogfd = open(Curl_dyn_ptr(&fname), QLOGMODE, - data->set.new_file_perms); - if(qlogfd != -1) - *qlogfdp = qlogfd; - } - Curl_dyn_free(&fname); - if(result) - return result; - } - - return CURLE_OK; -} -#endif +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * 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
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * 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 ENABLE_QUIC
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#include "urldata.h"
+#include "dynbuf.h"
+#include "curl_printf.h"
+#include "vquic.h"
+
+#ifdef O_BINARY
+#define QLOGMODE O_WRONLY|O_CREAT|O_BINARY
+#else
+#define QLOGMODE O_WRONLY|O_CREAT
+#endif
+
+/*
+ * If the QLOGDIR environment variable is set, open and return a file
+ * descriptor to write the log to.
+ *
+ * This function returns error if something failed outside of failing to
+ * create the file. Open file success is deemed by seeing if the returned fd
+ * is != -1.
+ */
+CURLcode Curl_qlogdir(struct Curl_easy *data,
+ unsigned char *scid,
+ size_t scidlen,
+ int *qlogfdp)
+{
+ const char *qlog_dir = getenv("QLOGDIR");
+ *qlogfdp = -1;
+ if(qlog_dir) {
+ struct dynbuf fname;
+ CURLcode result;
+ unsigned int i;
+ Curl_dyn_init(&fname, DYN_QLOG_NAME);
+ result = Curl_dyn_add(&fname, qlog_dir);
+ if(!result)
+ result = Curl_dyn_add(&fname, "/");
+ for(i = 0; (i < scidlen) && !result; i++) {
+ char hex[3];
+ msnprintf(hex, 3, "%02x", scid[i]);
+ result = Curl_dyn_add(&fname, hex);
+ }
+ if(!result)
+ result = Curl_dyn_add(&fname, ".sqlog");
+
+ if(!result) {
+ int qlogfd = open(Curl_dyn_ptr(&fname), QLOGMODE,
+ data->set.new_file_perms);
+ if(qlogfd != -1)
+ *qlogfdp = qlogfd;
+ }
+ Curl_dyn_free(&fname);
+ if(result)
+ return result;
+ }
+
+ return CURLE_OK;
+}
+#endif
diff --git a/libs/libcurl/src/vquic/vquic.h b/libs/libcurl/src/vquic/vquic.h index 8f599a8f49..958c587f92 100644 --- a/libs/libcurl/src/vquic/vquic.h +++ b/libs/libcurl/src/vquic/vquic.h @@ -1,36 +1,36 @@ -#ifndef HEADER_CURL_VQUIC_QUIC_H -#define HEADER_CURL_VQUIC_QUIC_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * 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 - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * 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 ENABLE_QUIC -CURLcode Curl_qlogdir(struct Curl_easy *data, - unsigned char *scid, - size_t scidlen, - int *qlogfdp); -#endif - -#endif /* HEADER_CURL_VQUIC_QUIC_H */ +#ifndef HEADER_CURL_VQUIC_QUIC_H
+#define HEADER_CURL_VQUIC_QUIC_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * 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
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * 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 ENABLE_QUIC
+CURLcode Curl_qlogdir(struct Curl_easy *data,
+ unsigned char *scid,
+ size_t scidlen,
+ int *qlogfdp);
+#endif
+
+#endif /* HEADER_CURL_VQUIC_QUIC_H */
|
