From f924489b25034c87f3d3acde9de0f09d660a1df7 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 5 Jun 2026 12:55:50 +0200 Subject: [PATCH] ngtcp2: share common functionality Share common functions/structs between ngtcp2 HTTP/3 and the proxy version. Fix bugs in proxy implementation when it comes to stream and pollset handling and transfer lifetimes. Curl_multi_xfer_sockbuf_borrow: work without multi When a connection gets shutdown by a share, the easy handle used is share->admin and it does not have a multi handle. In that case let Curl_multi_xfer_sockbuf_borrow() allocate a buffer to be freed on release. This happens when a TLS filter sends its last notify through a HTTP/3 proxy tunnel. Closes #21871 --- lib/Makefile.inc | 2 + lib/cf-ip-happy.c | 182 ++- lib/cf-ip-happy.h | 37 +- lib/cf-socket.c | 72 +- lib/cf-socket.h | 21 +- lib/connect.c | 69 +- lib/connect.h | 9 + lib/ftp.c | 10 +- lib/http_proxy.c | 46 +- lib/http_proxy.h | 5 +- lib/imap.c | 3 +- lib/multi.c | 20 +- lib/openldap.c | 3 +- lib/pop3.c | 3 +- lib/smtp.c | 3 +- lib/urldata.h | 2 + lib/vquic/cf-ngtcp2-cmn.c | 1965 ++++++++++++++++++++++ lib/vquic/cf-ngtcp2-cmn.h | 239 +++ lib/vquic/cf-ngtcp2-proxy.c | 2780 ++++---------------------------- lib/vquic/cf-ngtcp2-proxy.h | 19 +- lib/vquic/cf-ngtcp2.c | 2108 +----------------------- lib/vquic/cf-ngtcp2.h | 14 +- lib/vquic/cf-quiche.c | 58 +- lib/vquic/cf-quiche.h | 11 +- lib/vquic/vquic-tls.c | 42 +- lib/vquic/vquic-tls.h | 9 +- lib/vquic/vquic.c | 67 +- lib/vquic/vquic.h | 24 +- lib/vtls/apple.c | 2 +- lib/vtls/gtls.c | 6 +- lib/vtls/mbedtls.c | 4 +- lib/vtls/openssl.c | 37 +- lib/vtls/rustls.c | 2 +- lib/vtls/schannel.c | 10 +- lib/vtls/schannel_verify.c | 2 +- lib/vtls/vtls.c | 108 +- lib/vtls/vtls.h | 19 +- lib/vtls/vtls_scache.c | 115 +- lib/vtls/vtls_scache.h | 22 +- lib/vtls/wolfssl.c | 6 +- tests/http/test_06_eyeballs.py | 13 + tests/unit/unit2600.c | 17 +- tests/unit/unit3304.c | 38 +- 43 files changed, 3254 insertions(+), 4970 deletions(-) create mode 100644 lib/vquic/cf-ngtcp2-cmn.c create mode 100644 lib/vquic/cf-ngtcp2-cmn.h diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 88ca0a1ef2..1699fc5653 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -125,6 +125,7 @@ LIB_VQUIC_CFILES = \ vquic/capsule.c \ vquic/cf-capsule.c \ vquic/cf-ngtcp2.c \ + vquic/cf-ngtcp2-cmn.c \ vquic/cf-ngtcp2-proxy.c \ vquic/cf-quiche.c \ vquic/vquic.c \ @@ -134,6 +135,7 @@ LIB_VQUIC_HFILES = \ vquic/capsule.h \ vquic/cf-capsule.h \ vquic/cf-ngtcp2.h \ + vquic/cf-ngtcp2-cmn.h \ vquic/cf-ngtcp2-proxy.h \ vquic/cf-quiche.h \ vquic/vquic.h \ diff --git a/lib/cf-ip-happy.c b/lib/cf-ip-happy.c index 4f1787e784..08140b643a 100644 --- a/lib/cf-ip-happy.c +++ b/lib/cf-ip-happy.c @@ -62,7 +62,7 @@ struct transport_provider { cf_ip_connect_create *cf_create; uint8_t transport; - bool tunnel_proxy; + bool tunnel; }; static @@ -88,12 +88,12 @@ struct transport_provider transport_providers[] = { }; static cf_ip_connect_create *get_cf_create(uint8_t transport, - bool tunnel_proxy) + bool tunnel) { size_t i; for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) { if((transport == transport_providers[i].transport) && - (tunnel_proxy == transport_providers[i].tunnel_proxy)) + (tunnel == transport_providers[i].tunnel)) return transport_providers[i].cf_create; } return NULL; @@ -155,14 +155,17 @@ static bool cf_ai_iter_has_more(struct cf_ai_iter *iter, struct cf_ip_attempt { struct cf_ip_attempt *next; + struct Curl_peer *origin; + struct Curl_peer *peer; + struct Curl_peer *tunnel_peer; struct Curl_sockaddr_ex addr; struct Curl_cfilter *cf; /* current sub-cfilter connecting */ cf_ip_connect_create *cf_create; struct curltime started; /* start of current attempt */ CURLcode result; int ai_family; - uint8_t transport_in; - uint8_t transport_out; + uint8_t transport_peer; + uint8_t tunnel_transport; int error; BIT(connected); /* cf has connected */ BIT(shutdown); /* cf has shutdown */ @@ -176,17 +179,23 @@ static void cf_ip_attempt_free(struct cf_ip_attempt *a, if(a) { if(a->cf) Curl_conn_cf_discard_chain(&a->cf, data); + Curl_peer_unlink(&a->origin); + Curl_peer_unlink(&a->peer); + Curl_peer_unlink(&a->tunnel_peer); curlx_free(a); } } static CURLcode cf_ip_attempt_new(struct cf_ip_attempt **pa, - struct Curl_cfilter *cf, struct Curl_easy *data, + struct Curl_cfilter *cf, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct Curl_sockaddr_ex *addr, int ai_family, - uint8_t transport_in, - uint8_t transport_out, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport, cf_ip_connect_create *cf_create) { struct Curl_cfilter *wcf; @@ -198,16 +207,20 @@ static CURLcode cf_ip_attempt_new(struct cf_ip_attempt **pa, if(!a) return CURLE_OUT_OF_MEMORY; + Curl_peer_link(&a->origin, origin); + Curl_peer_link(&a->peer, peer); + a->transport_peer = transport_peer; + Curl_peer_link(&a->tunnel_peer, tunnel_peer); + a->tunnel_transport = tunnel_transport; a->addr = *addr; a->ai_family = ai_family; - a->transport_in = transport_in; - a->transport_out = transport_out; a->result = CURLE_OK; a->cf_create = cf_create; *pa = a; - result = a->cf_create(&a->cf, data, cf->conn, &a->addr, - a->transport_in, a->transport_out); + result = a->cf_create(&a->cf, data, a->origin, a->peer, a->transport_peer, + cf->conn, &a->addr, a->tunnel_peer, + a->tunnel_transport); if(result) goto out; @@ -256,14 +269,17 @@ struct cf_ip_ballers { #ifdef USE_IPV6 struct cf_ai_iter ipv6_iter; #endif + struct Curl_peer *origin; + struct Curl_peer *peer; + struct Curl_peer *tunnel_peer; cf_ip_connect_create *cf_create; /* for creating cf */ struct curltime started; struct curltime last_attempt_started; timediff_t attempt_delay_ms; int last_attempt_ai_family; uint32_t max_concurrent; - uint8_t transport_in; - uint8_t transport_out; + uint8_t transport_peer; + uint8_t tunnel_transport; }; static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a, @@ -281,8 +297,9 @@ static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a, a->inconclusive = FALSE; a->cf = NULL; - result = a->cf_create(&a->cf, data, cf->conn, &a->addr, a->transport_in, - a->transport_out); + result = a->cf_create(&a->cf, data, a->origin, a->peer, a->transport_peer, + cf->conn, &a->addr, + a->tunnel_peer, a->tunnel_transport); if(!result) { bool dummy; /* the new filter might have sub-filters */ @@ -295,11 +312,9 @@ static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a, return result; } -static void cf_ip_ballers_clear(struct Curl_cfilter *cf, - struct Curl_easy *data, +static void cf_ip_ballers_clear(struct Curl_easy *data, struct cf_ip_ballers *bs) { - (void)cf; while(bs->running) { struct cf_ip_attempt *a = bs->running; bs->running = a->next; @@ -307,37 +322,36 @@ static void cf_ip_ballers_clear(struct Curl_cfilter *cf, } cf_ip_attempt_free(bs->winner, data); bs->winner = NULL; + Curl_peer_unlink(&bs->origin); + Curl_peer_unlink(&bs->peer); + Curl_peer_unlink(&bs->tunnel_peer); } static CURLcode cf_ip_ballers_init(struct cf_ip_ballers *bs, - struct Curl_cfilter *cf, - cf_ip_connect_create *cf_create, - uint8_t transport_in, - uint8_t transport_out, + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport, timediff_t attempt_delay_ms, uint32_t max_concurrent) { memset(bs, 0, sizeof(*bs)); - bs->cf_create = cf_create; - bs->transport_in = transport_in; - bs->transport_out = transport_out; + bs->cf_create = get_cf_create(transport_peer, !!tunnel_peer); + if(!bs->cf_create) { + failf(data, "unsupported transport type %u%s", + transport_peer, tunnel_peer ? "to proxy" : ""); + return CURLE_UNSUPPORTED_PROTOCOL; + } + Curl_peer_link(&bs->origin, origin); + Curl_peer_link(&bs->peer, peer); + bs->transport_peer = transport_peer; + Curl_peer_link(&bs->tunnel_peer, tunnel_peer); + bs->tunnel_transport = tunnel_transport; bs->attempt_delay_ms = attempt_delay_ms; bs->max_concurrent = max_concurrent; bs->last_attempt_ai_family = AF_INET; /* so AF_INET6 is next */ - - if(transport_in == TRNSPRT_UNIX) { -#ifdef USE_UNIX_SOCKETS - cf_ai_iter_init(&bs->addr_iter, cf, AF_UNIX); -#else - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - else { /* TCP/UDP/QUIC */ -#ifdef USE_IPV6 - cf_ai_iter_init(&bs->ipv6_iter, cf, AF_INET6); -#endif - cf_ai_iter_init(&bs->addr_iter, cf, AF_INET); - } return CURLE_OK; } @@ -473,12 +487,13 @@ evaluate: if(bs->max_concurrent) cf_ip_ballers_prune(bs, cf, data, bs->max_concurrent - 1); - result = Curl_socket_addr_from_ai(&addr, ai, bs->transport_out); + result = Curl_socket_addr_from_ai(&addr, ai, bs->transport_peer); if(result) goto out; - result = cf_ip_attempt_new(&a, cf, data, &addr, ai_family, - bs->transport_in, bs->transport_out, + result = cf_ip_attempt_new(&a, data, cf, bs->origin, bs->peer, + bs->transport_peer, &addr, ai_family, + bs->tunnel_peer, bs->tunnel_transport, bs->cf_create); CURL_TRC_CF(data, cf, "starting %s attempt for ipv%s -> %d", bs->running ? "next" : "first", @@ -668,13 +683,10 @@ typedef enum { } cf_connect_state; struct cf_ip_happy_ctx { - struct Curl_peer *peer; cf_ip_connect_create *cf_create; cf_connect_state state; struct cf_ip_ballers ballers; struct curltime started; - uint8_t transport_in; - uint8_t transport_out; BIT(dns_resolved); }; @@ -750,29 +762,39 @@ static CURLcode cf_ip_happy_init(struct Curl_cfilter *cf, return CURLE_OPERATION_TIMEDOUT; } + if(ctx->ballers.transport_peer == TRNSPRT_UNIX) { +#ifdef USE_UNIX_SOCKETS + cf_ai_iter_init(&ctx->ballers.addr_iter, cf, AF_UNIX); +#else + return CURLE_UNSUPPORTED_PROTOCOL; +#endif + } + else { /* TCP/UDP/QUIC */ +#ifdef USE_IPV6 + cf_ai_iter_init(&ctx->ballers.ipv6_iter, cf, AF_INET6); +#endif + cf_ai_iter_init(&ctx->ballers.addr_iter, cf, AF_INET); + } + CURL_TRC_CF(data, cf, "init ip ballers for transport %u", - ctx->transport_out); + ctx->ballers.transport_peer); ctx->started = *Curl_pgrs_now(data); - return cf_ip_ballers_init(&ctx->ballers, cf, ctx->cf_create, - ctx->transport_in, ctx->transport_out, - data->set.happy_eyeballs_timeout, - IP_HE_MAX_CONCURRENT_ATTEMPTS); + return CURLE_OK; } -static void cf_ip_happy_ctx_clear(struct Curl_cfilter *cf, +static void cf_ip_happy_ctx_clear(struct cf_ip_happy_ctx *ctx, struct Curl_easy *data) { - struct cf_ip_happy_ctx *ctx = cf->ctx; - DEBUGASSERT(ctx); - DEBUGASSERT(data); - cf_ip_ballers_clear(cf, data, &ctx->ballers); + if(ctx) + cf_ip_ballers_clear(data, &ctx->ballers); } -static void cf_ip_happy_ctx_destroy(struct cf_ip_happy_ctx *ctx) +static void cf_ip_happy_ctx_destroy(struct cf_ip_happy_ctx *ctx, + struct Curl_easy *data) { if(ctx) { - Curl_peer_unlink(&ctx->peer); + cf_ip_happy_ctx_clear(ctx, data); curlx_free(ctx); } } @@ -860,7 +882,7 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf, cf->connected = TRUE; cf->next = ctx->ballers.winner->cf; ctx->ballers.winner->cf = NULL; - cf_ip_happy_ctx_clear(cf, data); + cf_ip_happy_ctx_clear(ctx, data); Curl_expire_done(data, EXPIRE_HAPPY_EYEBALLS); /* whatever errors where reported by ballers, clear our errorbuf */ Curl_reset_fail(data); @@ -943,8 +965,8 @@ static void cf_ip_happy_destroy(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "destroy"); if(ctx) { - cf_ip_happy_ctx_clear(cf, data); - cf_ip_happy_ctx_destroy(ctx); + cf_ip_happy_ctx_clear(ctx, data); + cf_ip_happy_ctx_destroy(ctx, data); } } @@ -977,11 +999,12 @@ struct Curl_cftype Curl_cft_ip_happy = { */ static CURLcode cf_ip_happy_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, - cf_ip_connect_create *cf_create, - uint8_t transport_in, - uint8_t transport_out) + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { struct cf_ip_happy_ctx *ctx = NULL; CURLcode result; @@ -994,42 +1017,39 @@ static CURLcode cf_ip_happy_create(struct Curl_cfilter **pcf, result = CURLE_OUT_OF_MEMORY; goto out; } - ctx->transport_in = transport_in; - ctx->transport_out = transport_out; - ctx->cf_create = cf_create; - Curl_peer_link(&ctx->peer, peer); + result = cf_ip_ballers_init(&ctx->ballers, data, + origin, peer, transport_peer, + tunnel_peer, tunnel_transport, + data->set.happy_eyeballs_timeout, + IP_HE_MAX_CONCURRENT_ATTEMPTS); + if(result) + goto out; result = Curl_cf_create(pcf, &Curl_cft_ip_happy, ctx); out: if(result) { curlx_safefree(*pcf); - cf_ip_happy_ctx_destroy(ctx); + cf_ip_happy_ctx_destroy(ctx, data); } return result; } CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, + struct Curl_peer *origin, struct Curl_peer *peer, - uint8_t transport_in, - uint8_t transport_out, - bool tunnel_proxy) + uint8_t transport_peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { - cf_ip_connect_create *cf_create; struct Curl_cfilter *cf; CURLcode result; /* Need to be first */ DEBUGASSERT(cf_at); - cf_create = get_cf_create(transport_out, tunnel_proxy); - if(!cf_create) { - CURL_TRC_CF(data, cf_at, "unsupported transport type %u%s", - transport_out, tunnel_proxy ? "to proxy" : ""); - return CURLE_UNSUPPORTED_PROTOCOL; - } - result = cf_ip_happy_create(&cf, data, peer, cf_at->conn, cf_create, - transport_in, transport_out); + result = cf_ip_happy_create(&cf, data, origin, peer, transport_peer, + cf_at->conn, tunnel_peer, tunnel_transport); if(result) return result; diff --git a/lib/cf-ip-happy.h b/lib/cf-ip-happy.h index 90cecae889..d2994aad43 100644 --- a/lib/cf-ip-happy.h +++ b/lib/cf-ip-happy.h @@ -33,11 +33,15 @@ struct Curl_peer; struct Curl_sockaddr_ex; /** - * Create a cfilter for making an "ip" connection to the - * given address, using parameters from `conn`. The "ip" connection - * can be a TCP socket, a UDP socket or even a QUIC connection. - * - * It MUST use only the supplied `ai` for its connection attempt. + * Create a cfilter for making an "ip" connect to a peer. + * `pcf`: the filter created on success + * `data`: the transfer initiating the connect + * `peer`: the peer to connect to + * `transport_peer': the transport used for the peer connect + * `conn`: the connection that gets connected + * `addr`: the socket address to connect to + * `tunnel_peer`: NULL or the peer to tunnel through + * `tunnel_transport`: the transport that goes through the tunnel * * Such a filter may be used in "happy eyeball" scenarios, and its * `connect` implementation needs to support non-blocking. Once connected, @@ -45,26 +49,21 @@ struct Curl_sockaddr_ex; */ typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, + struct Curl_peer *origin, struct Curl_peer *peer, - uint8_t transport_in, - uint8_t transport_out, - bool tunnel_proxy); - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3) && \ - defined(USE_PROXY_HTTP3) -/* For H3 proxy: create happy eyeballs that races IPv4/IPv6 using raw UDP - sockets with TRNSPRT_QUIC transport so the socket is connected to the - proxy peer. H3-PROXY manages its own ngtcp2 QUIC stack on top. */ -CURLcode cf_ip_happy_quic_udp_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data); -#endif /* !CURL_DISABLE_HTTP && USE_HTTP3 && USE_PROXY_HTTP3 */ + uint8_t transport_peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); extern struct Curl_cftype Curl_cft_ip_happy; diff --git a/lib/cf-socket.c b/lib/cf-socket.c index 354f43e7ea..a556b8f8da 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -906,7 +906,7 @@ static CURLcode socket_connect_result(struct Curl_easy *data, } struct cf_socket_ctx { - uint8_t transport; + struct Curl_peer *peer; struct Curl_sockaddr_ex addr; /* address to connect to */ curl_socket_t sock; /* current attempt socket */ struct ip_quadruple ip; /* The IP quadruple 2x(addr+port) */ @@ -924,6 +924,7 @@ struct cf_socket_ctx { int rblock_percent; /* percent of reads doing EAGAIN */ size_t recv_max; /* max enforced read size */ #endif + uint8_t transport; BIT(got_first_byte); /* if first byte was received */ BIT(listening); /* socket is listening */ BIT(accepted); /* socket was accepted, not connected */ @@ -932,10 +933,12 @@ struct cf_socket_ctx { }; static CURLcode cf_socket_ctx_init(struct cf_socket_ctx *ctx, + struct Curl_peer *peer, struct Curl_sockaddr_ex *addr, uint8_t transport) { memset(ctx, 0, sizeof(*ctx)); + Curl_peer_link(&ctx->peer, peer); ctx->sock = CURL_SOCKET_BAD; ctx->transport = transport; ctx->addr = *addr; @@ -972,6 +975,14 @@ static CURLcode cf_socket_ctx_init(struct cf_socket_ctx *ctx, return CURLE_OK; } +static void cf_socket_ctx_free(struct cf_socket_ctx *ctx) +{ + if(ctx) { + Curl_peer_unlink(&ctx->peer); + curlx_free(ctx); + } +} + static CURLcode cf_socket_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) @@ -1006,7 +1017,7 @@ static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; socket_close(data, cf->conn, !ctx->accepted, ctx->sock); } - curlx_free(ctx); + cf_socket_ctx_free(ctx); } } @@ -1754,19 +1765,24 @@ struct Curl_cftype Curl_cft_tcp = { CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out) + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { struct cf_socket_ctx *ctx = NULL; struct Curl_cfilter *cf = NULL; CURLcode result; (void)data; + (void)origin; (void)conn; - (void)transport_in; - DEBUGASSERT(transport_out == TRNSPRT_TCP); + (void)tunnel_peer; + (void)tunnel_transport; + DEBUGASSERT(transport_peer == TRNSPRT_TCP); if(!addr) { result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; @@ -1778,7 +1794,7 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, goto out; } - result = cf_socket_ctx_init(ctx, addr, transport_out); + result = cf_socket_ctx_init(ctx, peer, addr, transport_peer); if(result) goto out; @@ -1788,7 +1804,7 @@ out: *pcf = (!result) ? cf : NULL; if(result) { curlx_safefree(cf); - curlx_safefree(ctx); + cf_socket_ctx_free(ctx); } return result; @@ -1921,26 +1937,31 @@ struct Curl_cftype Curl_cft_udp = { CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out) + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { struct cf_socket_ctx *ctx = NULL; struct Curl_cfilter *cf = NULL; CURLcode result; (void)data; + (void)origin; (void)conn; - (void)transport_in; - DEBUGASSERT(transport_out == TRNSPRT_UDP || transport_out == TRNSPRT_QUIC); + (void)tunnel_peer; + (void)tunnel_transport; + DEBUGASSERT(transport_peer == TRNSPRT_UDP || transport_peer == TRNSPRT_QUIC); ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - result = cf_socket_ctx_init(ctx, addr, transport_out); + result = cf_socket_ctx_init(ctx, peer, addr, transport_peer); if(result) goto out; @@ -1950,7 +1971,7 @@ out: *pcf = (!result) ? cf : NULL; if(result) { curlx_safefree(cf); - curlx_safefree(ctx); + cf_socket_ctx_free(ctx); } return result; @@ -1975,27 +1996,32 @@ struct Curl_cftype Curl_cft_unix = { }; CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, - struct Curl_easy *data, - struct connectdata *conn, - struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out) + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, + struct connectdata *conn, + struct Curl_sockaddr_ex *addr, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { struct cf_socket_ctx *ctx = NULL; struct Curl_cfilter *cf = NULL; CURLcode result; (void)data; + (void)origin; (void)conn; - (void)transport_in; - DEBUGASSERT(transport_out == TRNSPRT_UNIX); + (void)tunnel_peer; + (void)tunnel_transport; + DEBUGASSERT(transport_peer == TRNSPRT_UNIX); ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - result = cf_socket_ctx_init(ctx, addr, transport_out); + result = cf_socket_ctx_init(ctx, peer, addr, transport_peer); if(result) goto out; @@ -2005,7 +2031,7 @@ out: *pcf = (!result) ? cf : NULL; if(result) { curlx_safefree(cf); - curlx_safefree(ctx); + cf_socket_ctx_free(ctx); } return result; diff --git a/lib/cf-socket.h b/lib/cf-socket.h index 9c1f3bf4b4..767fd30e15 100644 --- a/lib/cf-socket.h +++ b/lib/cf-socket.h @@ -94,10 +94,13 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, */ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); /** * Creates a cfilter that opens a UDP socket to the given address @@ -108,10 +111,13 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, */ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); /** * Creates a cfilter that opens a UNIX socket to the given address @@ -122,10 +128,13 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, */ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); /** * Creates a cfilter that keeps a listening socket. diff --git a/lib/connect.c b/lib/connect.c index 8578aed73d..b47e55e48e 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -413,7 +413,8 @@ static CURLcode cf_setup_add_http_proxy(struct Curl_cfilter *cf, #ifdef USE_SSL if(IS_HTTPS_PROXY(cf->conn->http_proxy.proxytype) && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { - result = Curl_cf_ssl_proxy_insert_after(cf, data); + result = Curl_cf_ssl_proxy_insert_after( + cf, data, cf->conn->http_proxy.peer); if(result) { CURL_TRC_CF(data, cf, "adding SSL filter for HTTP proxy failed -> %d", result); @@ -424,10 +425,12 @@ static CURLcode cf_setup_add_http_proxy(struct Curl_cfilter *cf, #endif /* USE_SSL */ if(cf->conn->bits.tunnel_proxy) { - struct Curl_peer *dest; /* where HTTP should tunnel to */ - dest = Curl_conn_get_destination(cf->conn, cf->sockindex); + struct Curl_peer *peer = cf->conn->http_proxy.peer; + struct Curl_peer *tunnel_peer; /* where HTTP should tunnel to */ + tunnel_peer = Curl_conn_get_destination(cf->conn, cf->sockindex); result = Curl_cf_http_proxy_insert_after( - cf, data, dest, ctx->transport, cf->conn->http_proxy.proxytype); + cf, data, peer, tunnel_peer, + ctx->transport, cf->conn->http_proxy.proxytype); if(result) { CURL_TRC_CF(data, cf, "adding HTTP proxy tunnel filter failed -> %d", result); @@ -449,41 +452,47 @@ static CURLcode cf_setup_add_ip_happy(struct Curl_cfilter *cf, CURLcode result = CURLE_OK; if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) { - /* What is the fist hop we directly connect to and what transport - * do we use for it? Only on the first hop we can do Happy Eyeballs. */ + /* What is the first hop we directly connect to and what transport + * do we use for it? Only on the first hop we can do Happy Eyeballs. + * first_origin and first_peer differ on --connect-to. */ + struct Curl_peer *first_origin = + Curl_conn_get_first_origin(cf->conn, cf->sockindex); struct Curl_peer *first_peer = Curl_conn_get_first_peer(cf->conn, cf->sockindex); + struct Curl_peer *tunnel_peer = NULL; uint8_t first_transport = ctx->transport; - bool tunnel_proxy = FALSE; + + if(!first_peer) + return CURLE_FAILED_INIT; #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) if(cf->conn->bits.httpproxy && cf->conn->bits.tunnel_proxy) { first_transport = Curl_http_proxy_transport(cf->conn->http_proxy.proxytype); + tunnel_peer = Curl_conn_get_destination(cf->conn, cf->sockindex); if((first_transport == TRNSPRT_QUIC) && (cf->conn->bits.socksproxy)) { failf(data, "HTTP/3 proxy not possible via SOCKS"); return CURLE_UNSUPPORTED_PROTOCOL; } - tunnel_proxy = TRUE; } #endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ - result = cf_ip_happy_insert_after(cf, data, first_peer, - ctx->transport, first_transport, - tunnel_proxy); + result = cf_ip_happy_insert_after(cf, data, first_origin, first_peer, + first_transport, + tunnel_peer, ctx->transport); if(result) { CURL_TRC_CF(data, cf, "adding happy eyeballs failed -> %d", result); return result; } - if(tunnel_proxy && (first_transport == TRNSPRT_QUIC)) { + if(tunnel_peer && (first_transport == TRNSPRT_QUIC)) { CURL_TRC_CF(data, cf, "happy eyeballing to HTTP/3 proxy %s:%u", first_peer->hostname, first_peer->port); ctx->state = CF_SETUP_CNNCT_HTTP_PROXY; } else { CURL_TRC_CF(data, cf, "happy eyeballing to %s %s:%u", - tunnel_proxy ? "proxy" : "origin", + tunnel_peer ? "proxy" : "origin", first_peer->hostname, first_peer->port); ctx->state = CF_SETUP_CNNCT_EYEBALLS; } @@ -501,17 +510,22 @@ static CURLcode cf_setup_add_origin_filters(struct Curl_cfilter *cf, if(ctx->state < CF_SETUP_CNNCT_SSL) { #if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3) && \ !defined(CURL_DISABLE_PROXY) + /* Wanting QUIC with a HTTP tunneling filter, we now need to add * the QUIC filter on top. Without tunneling, this has already * happened in the Happy Eyeball filter. */ if(ctx->transport == TRNSPRT_QUIC && cf->conn->bits.httpproxy && cf->conn->bits.tunnel_proxy) { + struct Curl_peer *origin = Curl_conn_get_origin(cf->conn, cf->sockindex); + struct Curl_peer *peer = + Curl_conn_get_destination(cf->conn, cf->sockindex); + result = Curl_cf_capsule_insert_after(cf, data); if(result) { CURL_TRC_CF(data, cf, "adding capsule filter failed -> %d", result); return result; } - result = Curl_cf_quic_insert_after(cf); + result = Curl_cf_quic_insert_after(cf, origin, peer); if(result) { CURL_TRC_CF(data, cf, "adding QUIC filter failed -> %d", result); return result; @@ -525,7 +539,13 @@ static CURLcode cf_setup_add_origin_filters(struct Curl_cfilter *cf, (ctx->ssl_mode != CURL_CF_SSL_DISABLE && cf->conn->scheme->flags & PROTOPT_SSL)) && /* we want SSL */ !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */ - result = Curl_cf_ssl_insert_after(cf, data); + /* Another FTP quirk: when adding SSL verification, to a DATA + * connection, always verify against the control's origin */ + struct Curl_peer *origin = Curl_conn_get_origin(cf->conn, FIRSTSOCKET); + struct Curl_peer *peer = + Curl_conn_get_destination(cf->conn, cf->sockindex); + + result = Curl_cf_ssl_insert_after(cf, data, origin, peer); if(result) { CURL_TRC_CF(data, cf, "adding SSL filter for origin failed -> %d", result); @@ -777,6 +797,13 @@ void Curl_conn_set_multiplex(struct connectdata *conn) } } +struct Curl_peer *Curl_conn_get_origin(struct connectdata *conn, + int sockindex) +{ + return (sockindex == SECONDARYSOCKET) ? + conn->origin2 : conn->origin; +} + struct Curl_peer *Curl_conn_get_destination(struct connectdata *conn, int sockindex) { @@ -789,6 +816,18 @@ struct Curl_peer *Curl_conn_get_destination(struct connectdata *conn, (conn->via_peer ? conn->via_peer : conn->origin); } +struct Curl_peer *Curl_conn_get_first_origin(struct connectdata *conn, + int sockindex) +{ +#ifndef CURL_DISABLE_PROXY + if(conn->socks_proxy.peer) + return conn->socks_proxy.peer; + if(conn->http_proxy.peer) + return conn->http_proxy.peer; +#endif + return (sockindex == SECONDARYSOCKET) ? conn->origin2 : conn->origin; +} + struct Curl_peer *Curl_conn_get_first_peer(struct connectdata *conn, int sockindex) { diff --git a/lib/connect.h b/lib/connect.h index 65e1ab1ea7..968314ea8e 100644 --- a/lib/connect.h +++ b/lib/connect.h @@ -126,12 +126,21 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, /* Set conn to allow multiplexing. */ void Curl_conn_set_multiplex(struct connectdata *conn); +/* Get the origin peer at sockindex. */ +struct Curl_peer *Curl_conn_get_origin(struct connectdata *conn, + int sockindex); + /* Get the peer the connection actually connects to at sockindex. * Often the same as "origin", but can be redirected via "connect-to" * or "alt-svc". May tunnel through proxies. */ struct Curl_peer *Curl_conn_get_destination(struct connectdata *conn, int sockindex); +/* Get the origin curl connects its socket to. + * Can be origin or the first proxy. */ +struct Curl_peer *Curl_conn_get_first_origin(struct connectdata *conn, + int sockindex); + /* Get the peer curl connects its socket to. * Can be origin, "connect-to" or the first proxy. */ struct Curl_peer *Curl_conn_get_first_peer(struct connectdata *conn, diff --git a/lib/ftp.c b/lib/ftp.c index 864fb1509c..9908d50afb 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -1390,10 +1390,13 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, ftp_state(data, ftpc, FTP_STOP); } else { - /* successfully set up the listen socket filter. SSL needed? */ + /* successfully set up the listen socket filter. SSL needed? + * Use the control connections origin for cert verification. */ if(conn->bits.ftp_use_data_ssl && data->set.ftp_use_port && !Curl_conn_is_ssl(conn, SECONDARYSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, SECONDARYSOCKET); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), + conn, SECONDARYSOCKET); } conn->bits.do_more = FALSE; Curl_pgrsTime(data, TIMER_STARTACCEPT); @@ -3196,7 +3199,8 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, /* this was BLOCKING, keep it so for now */ bool done; if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), conn, FIRSTSOCKET); if(result) { /* we failed and bail out */ return CURLE_USE_SSL_FAILED; diff --git a/lib/http_proxy.c b/lib/http_proxy.c index 8c3be63b12..b01affeeb9 100644 --- a/lib/http_proxy.c +++ b/lib/http_proxy.c @@ -172,10 +172,11 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, } struct cf_proxy_ctx { - struct Curl_peer *dest; /* tunnel destination */ + struct Curl_peer *peer; /* proxy */ + struct Curl_peer *tunnel_peer; /* tunnel destination */ uint8_t proxytype; + uint8_t tunnel_transport; BIT(sub_filter_installed); - BIT(udp_tunnel); }; static int proxy_http_ver_major(proxy_http_ver ver) @@ -556,9 +557,8 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, { struct cf_proxy_ctx *ctx = cf->ctx; CURLcode result; - const char *tunnel_type; /* Determine tunnel type once and reuse */ - - tunnel_type = ctx->udp_tunnel ? "CONNECT-UDP" : "CONNECT"; + bool udp_tunnel = TRNSPRT_IS_DGRAM(ctx->tunnel_transport); + const char *tunnel_type = udp_tunnel ? "CONNECT-UDP" : "CONNECT"; if(cf->connected) { *done = TRUE; @@ -606,8 +606,8 @@ connect_sub: if(!strcmp(alpn, "http/1.0")) { CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.0"); - result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->dest, 10, - (bool)ctx->udp_tunnel); + result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->tunnel_peer, 10, + udp_tunnel); if(result) goto out; } @@ -615,16 +615,16 @@ connect_sub: int httpversion = (ctx->proxytype == CURLPROXY_HTTP_1_0) ? 10 : 11; CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.%d", httpversion % 10); - result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->dest, httpversion, - (bool)ctx->udp_tunnel); + result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->tunnel_peer, + httpversion, udp_tunnel); if(result) goto out; } #ifdef USE_NGHTTP2 else if(!strcmp(alpn, "h2")) { CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2"); - result = Curl_cf_h2_proxy_insert_after(cf, data, ctx->dest, - (bool)ctx->udp_tunnel); + result = Curl_cf_h2_proxy_insert_after(cf, data, ctx->tunnel_peer, + udp_tunnel); if(result) goto out; } @@ -633,8 +633,9 @@ connect_sub: defined(USE_NGTCP2) && defined(USE_OPENSSL) else if(!strcmp(alpn, "h3")) { CURL_TRC_CF(data, cf, "installing subfilter for HTTP/3"); - result = Curl_cf_h3_proxy_insert_after(cf, data, ctx->dest, - (bool)ctx->udp_tunnel); + result = Curl_cf_h3_proxy_insert_after(cf, data, ctx->peer, ctx->peer, + ctx->tunnel_peer, + ctx->tunnel_transport); if(result) goto out; } @@ -673,8 +674,8 @@ static CURLcode cf_http_proxy_query(struct Curl_cfilter *cf, struct cf_proxy_ctx *ctx = cf->ctx; switch(query) { case CF_QUERY_HOST_PORT: - *pres1 = (int)ctx->dest->port; - *((const char **)pres2) = ctx->dest->hostname; + *pres1 = (int)ctx->tunnel_peer->port; + *((const char **)pres2) = ctx->tunnel_peer->hostname; return CURLE_OK; case CF_QUERY_ALPN_NEGOTIATED: { const char **palpn = pres2; @@ -693,7 +694,8 @@ static CURLcode cf_http_proxy_query(struct Curl_cfilter *cf, static void cf_https_proxy_ctx_free(struct cf_proxy_ctx *ctx) { if(ctx) { - Curl_peer_unlink(&ctx->dest); + Curl_peer_unlink(&ctx->peer); + Curl_peer_unlink(&ctx->tunnel_peer); curlx_free(ctx); } } @@ -727,8 +729,9 @@ struct Curl_cftype Curl_cft_http_proxy = { CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - struct Curl_peer *dest, - uint8_t transport, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport, uint8_t proxytype) { struct Curl_cfilter *cf; @@ -736,7 +739,7 @@ CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, CURLcode result; (void)data; - if(!dest) + if(!peer || !tunnel_peer) return CURLE_FAILED_INIT; ctx = curlx_calloc(1, sizeof(*ctx)); @@ -744,9 +747,10 @@ CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, result = CURLE_OUT_OF_MEMORY; goto out; } - Curl_peer_link(&ctx->dest, dest); + Curl_peer_link(&ctx->peer, peer); + Curl_peer_link(&ctx->tunnel_peer, tunnel_peer); ctx->proxytype = proxytype; - ctx->udp_tunnel = (transport == TRNSPRT_QUIC); + ctx->tunnel_transport = tunnel_transport; result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx); if(result) diff --git a/lib/http_proxy.h b/lib/http_proxy.h index ef4becdacf..b60bad96f6 100644 --- a/lib/http_proxy.h +++ b/lib/http_proxy.h @@ -68,8 +68,9 @@ CURLcode Curl_http_proxy_inspect_tunnel_response( CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - struct Curl_peer *dest, - uint8_t transport, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport, uint8_t proxytype); extern struct Curl_cftype Curl_cft_http_proxy; diff --git a/lib/imap.c b/lib/imap.c index 87d33c9bce..abb43ea2d8 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -555,7 +555,8 @@ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, bool ssldone = FALSE; if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), conn, FIRSTSOCKET); if(result) goto out; /* Change the connection handler */ diff --git a/lib/multi.c b/lib/multi.c index dd29328a17..14fbc31259 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -4074,11 +4074,12 @@ CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, size_t blen, char **pbuf) { DEBUGASSERT(data); - DEBUGASSERT(data->multi); *pbuf = NULL; if(!data->multi) { - failf(data, "transfer has no multi handle"); - return CURLE_FAILED_INIT; + /* When a SHARE gets destroyed and has a connection pool, we get + * call with share->admin which does not have a multi handle. */ + *pbuf = curlx_malloc(blen); + return *pbuf ? CURLE_OK : CURLE_OUT_OF_MEMORY; } if(data->multi->xfer_sockbuf_borrowed) { failf(data, "attempt to borrow xfer_sockbuf when already borrowed"); @@ -4107,11 +4108,16 @@ CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, void Curl_multi_xfer_sockbuf_release(struct Curl_easy *data, char *buf) { - (void)buf; DEBUGASSERT(data); - DEBUGASSERT(data->multi); - DEBUGASSERT(!buf || data->multi->xfer_sockbuf == buf); - data->multi->xfer_sockbuf_borrowed = FALSE; + if(!data->multi) { + /* When a SHARE gets destroyed and has a connection pool, we get + * call with share->admin which does not have a multi handle. */ + curlx_free(buf); + } + else { + DEBUGASSERT(!buf || data->multi->xfer_sockbuf == buf); + data->multi->xfer_sockbuf_borrowed = FALSE; + } } static void multi_xfer_bufs_free(struct Curl_multi *multi) diff --git a/lib/openldap.c b/lib/openldap.c index 58b31b32af..8ec6bb27cd 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -900,7 +900,8 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done) result = oldap_perform_bind(data, OLDAP_BIND); break; } - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), conn, FIRSTSOCKET); if(result) break; FALLTHROUGH(); diff --git a/lib/pop3.c b/lib/pop3.c index 3036ce717c..56157af291 100644 --- a/lib/pop3.c +++ b/lib/pop3.c @@ -485,7 +485,8 @@ static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data, return CURLE_FAILED_INIT; if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), conn, FIRSTSOCKET); if(result) goto out; /* Change the connection handler */ diff --git a/lib/smtp.c b/lib/smtp.c index dee7a329ca..6bda7ae814 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -689,7 +689,8 @@ static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data, DEBUGASSERT(smtpc->state == SMTP_UPGRADETLS); if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + result = Curl_ssl_cfilter_add( + data, Curl_conn_get_origin(conn, FIRSTSOCKET), conn, FIRSTSOCKET); if(result) goto out; /* Change the connection handler and SMTP state */ diff --git a/lib/urldata.h b/lib/urldata.h index f746b5c301..97cfc5efc4 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -253,6 +253,8 @@ struct hostname { #define TRNSPRT_QUIC 5 #define TRNSPRT_UNIX 6 +#define TRNSPRT_IS_DGRAM(x) (((x) == TRNSPRT_UDP) || ((x) == TRNSPRT_QUIC)) + struct ip_quadruple { char remote_ip[MAX_IPADR_LEN]; char local_ip[MAX_IPADR_LEN]; diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c new file mode 100644 index 0000000000..52422c22e4 --- /dev/null +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -0,0 +1,1965 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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" + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_NGTCP2) && defined(USE_NGHTTP3) + +#include + +#ifdef USE_OPENSSL +#include +#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL) +#include +#elif defined(OPENSSL_QUIC_API2) +#include +#else +#include +#endif +#include "vtls/openssl.h" +#elif defined(USE_GNUTLS) +#include +#include "vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include +#include "vtls/wolfssl.h" +#endif + +#include + +#include "urldata.h" +#include "url.h" +#include "uint-hash.h" +#include "curl_trc.h" +#include "rand.h" +#include "multiif.h" +#include "cfilters.h" +#include "cf-dns.h" +#include "cf-socket.h" +#include "connect.h" +#include "progress.h" +#include "curlx/fopen.h" +#include "curlx/dynbuf.h" +#include "http1.h" +#include "select.h" +#include "transfer.h" +#include "bufref.h" +#include "vquic/vquic.h" +#include "vquic/vquic_int.h" +#include "vquic/vquic-tls.h" +#include "vtls/vtls.h" +#include "vtls/vtls_scache.h" +#include "vquic/cf-ngtcp2-cmn.h" + +/* + * Store ngtcp2 version info in this buffer. + */ +void Curl_ngtcp2_ver(char *p, size_t len) +{ + const ngtcp2_info *ng2 = ngtcp2_version(0); + const nghttp3_info *ht3 = nghttp3_version(0); + (void)curl_msnprintf(p, len, "ngtcp2/%s nghttp3/%s", + ng2->version_str, ht3->version_str); +} + +void Curl_cf_ngtcp2_h3_stream_ctx_free(struct h3_stream_ctx *stream) +{ + Curl_bufq_free(&stream->sendbuf); + Curl_h1_req_parse_free(&stream->h1); + curlx_free(stream); +} + +static void h3_stream_hash_free(unsigned int id, void *stream) +{ + (void)id; + DEBUGASSERT(stream); + Curl_cf_ngtcp2_h3_stream_ctx_free((struct h3_stream_ctx *)stream); +} + +static bool cf_ngtcp2_h3_err_is_fatal(int code) +{ + return (NGHTTP3_ERR_FATAL >= code) || + (NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM == code); +} + +void Curl_cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + if(!ctx->last_error.error_code) { + ngtcp2_ccerr_set_application_error(&ctx->last_error, + nghttp3_err_infer_quic_app_error_code(code), NULL, 0); + } + if(cf_ngtcp2_h3_err_is_fatal(code)) + Curl_cf_ngtcp2_cmn_conn_close(cf, data); +} + +CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, + cf_ngtcp2_init_h3_conn *init_h3_conn_cb) +{ + DEBUGASSERT(!ctx->initialized); + ctx->qlogfd = -1; + ctx->tunnel_inbuf = NULL; + ctx->tunnel_inbuf_len = 0; + ctx->version = NGTCP2_PROTO_VER_MAX; + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); + Curl_uint32_hash_init(&ctx->streams, 63, h3_stream_hash_free); + ctx->init_h3_conn_cb = init_h3_conn_cb; + ctx->initialized = TRUE; + return Curl_vquic_tls_peer_init(origin, peer, sslc, &ctx->ssl_peer); +} + +void Curl_cf_ngtcp2_ctx_cleanup(struct cf_ngtcp2_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_vquic_tls_cleanup(&ctx->tls); + vquic_ctx_free(&ctx->q); + Curl_bufcp_free(&ctx->stream_bufcp); + curlx_dyn_free(&ctx->scratch); + Curl_uint32_hash_destroy(&ctx->streams); + Curl_ssl_peer_cleanup(&ctx->ssl_peer); + curlx_safefree(ctx->tunnel_inbuf); + ctx->tunnel_inbuf_len = 0; + if(ctx->qlogfd != -1) { + curlx_close(ctx->qlogfd); + ctx->qlogfd = -1; + } + } +} + +static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) +{ + struct Curl_cfilter *cf = conn_ref->user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + return ctx->qconn; +} + +#ifdef DEBUG_NGTCP2 +static void quic_printf(void *user_data, const char *fmt, ...) +{ + va_list ap; + (void)user_data; + va_start(ap, fmt); + curl_mvfprintf(stderr, fmt, ap); + va_end(ap); + curl_mfprintf(stderr, "\n"); +} +#endif + +static void qlog_callback(void *user_data, uint32_t flags, + const void *data, size_t datalen) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + (void)flags; + if(ctx->qlogfd != -1) { + ssize_t rc = write(ctx->qlogfd, data, datalen); + if(rc == -1) { + /* on write error, stop further write attempts */ + curlx_close(ctx->qlogfd); + ctx->qlogfd = -1; + } + } +} + +static void quic_settings(struct cf_ngtcp2_ctx *ctx, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx) +{ + ngtcp2_settings *s = &ctx->settings; + ngtcp2_transport_params *t = &ctx->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 = pktx->ts; + s->handshake_timeout = (data->set.connecttimeout > 0) ? + data->set.connecttimeout * NGTCP2_MILLISECONDS : QUIC_HANDSHAKE_TIMEOUT; + s->max_window = H3_CONN_WINDOW_SIZE_MAX; + s->max_stream_window = 0; /* disable ngtcp2 auto-tuning of window */ + s->no_pmtud = FALSE; +#ifdef NGTCP2_SETTINGS_V3 + /* try ten times the ngtcp2 defaults here for problems with Caddy */ + s->glitch_ratelim_burst = 1000 * 10; + s->glitch_ratelim_rate = 33 * 10; +#endif + t->initial_max_data = s->max_window; + t->initial_max_stream_data_bidi_local = H3_STREAM_WINDOW_SIZE_INITIAL; + t->initial_max_stream_data_bidi_remote = H3_STREAM_WINDOW_SIZE_INITIAL; + t->initial_max_stream_data_uni = t->initial_max_data; + t->initial_max_streams_bidi = QUIC_MAX_STREAMS; + t->initial_max_streams_uni = QUIC_MAX_STREAMS; + t->max_idle_timeout = 0; /* no idle timeout from our side */ + if(ctx->qlogfd != -1) { + s->qlog_write = qlog_callback; + } +} + +#if defined(_MSC_VER) && defined(_DLL) +#pragma warning(push) +#pragma warning(disable:4232) /* MSVC extension, dllimport identity */ +#endif + +static int cb_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; + struct Curl_easy *data; + + (void)tconn; + DEBUGASSERT(ctx); + data = CF_DATA_CURRENT(cf); + DEBUGASSERT(data); + if(!ctx || !data) + return NGTCP2_ERR_CALLBACK_FAILURE; + + ctx->handshake_at = *Curl_pgrs_now(data); + ctx->tls_handshake_complete = TRUE; + Curl_vquic_report_handshake(&ctx->tls, cf, data); + + ctx->tls_vrfy_result = Curl_vquic_tls_verify_peer(&ctx->tls, cf, + data, &ctx->ssl_peer); + if(ctx->tls_vrfy_result) + return NGTCP2_ERR_CALLBACK_FAILURE; + +#ifdef CURLVERBOSE + if(Curl_trc_is_verbose(data)) { + const ngtcp2_transport_params *rp; + rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); + CURL_TRC_CF(data, cf, "handshake complete after %" FMT_TIMEDIFF_T + "ms, remote transport[max_udp_payload=%" PRIu64 + ", initial_max_data=%" PRIu64 "]", + curlx_ptimediff_ms(&ctx->handshake_at, &ctx->started_at), + rp->max_udp_payload_size, rp->initial_max_data); + } +#endif + + /* In case of earlydata, where we simulate being connected, update + * the handshake time when we really did connect */ + if(ctx->use_earlydata) + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); + if(ctx->use_earlydata) { +#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) + ctx->earlydata_accepted = + (SSL_get_early_data_status(ctx->tls.ossl.ssl) != + SSL_EARLY_DATA_REJECTED); +#endif +#ifdef USE_GNUTLS + int flags = gnutls_session_get_flags(ctx->tls.gtls.session); + ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA); +#endif +#ifdef USE_WOLFSSL +#ifdef WOLFSSL_EARLY_DATA + ctx->earlydata_accepted = + (wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) != + WOLFSSL_EARLY_DATA_REJECTED); +#else + DEBUGASSERT(0); /* should not come here if ED is disabled. */ + ctx->earlydata_accepted = FALSE; +#endif /* WOLFSSL_EARLY_DATA */ +#endif + CURL_TRC_CF(data, cf, "server did%s accept %zu bytes of early data", + ctx->earlydata_accepted ? "" : " not", ctx->earlydata_skip); + Curl_pgrsEarlyData(data, ctx->earlydata_accepted ? + (curl_off_t)ctx->earlydata_skip : + -(curl_off_t)ctx->earlydata_skip); + } + return 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 Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + nghttp3_ssize rc; + uint64_t nconsumed; + int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)offset; + + rc = nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin); + if(rc < 0) { + if(data && stream) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] error on known stream, " + "reset=%d, closed=%d", + stream_id, stream->reset, stream->closed); + } + return NGTCP2_ERR_CALLBACK_FAILURE; + } + nconsumed = (uint64_t)rc; + if(nconsumed) { + /* 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); + if(stream) { + stream->rx_offset += nconsumed; + stream->rx_offset_max += 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 Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + (void)stream_id; + (void)tconn; + (void)offset; + (void)datalen; + (void)stream_user_data; + + rv = nghttp3_conn_add_ack_offset(ctx->h3conn, stream_id, datalen); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + 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 Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + int rv; + + (void)tconn; + /* stream is closed... */ + if(!data) + data = CF_DATA_CURRENT(cf); + if(!data) + return NGTCP2_ERR_CALLBACK_FAILURE; + + if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + + rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id, app_error_code); + CURL_TRC_CF(data, cf, "[%" PRId64 "] quic close(app_error=%" + PRIu64 ") -> %d", stream_id, app_error_code, rv); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + Curl_cf_ngtcp2_h3_err_set(cf, data, rv); + 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 Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + int rv; + (void)tconn; + (void)final_size; + (void)app_error_code; + + rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); + CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + 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 Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + (void)tconn; + (void)app_error_code; + (void)stream_user_data; + + rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + 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) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + (void)tconn; + ctx->max_bidi_streams = max_streams; + if(data) + CURL_TRC_CF(data, cf, "max bidi streams now %" PRIu64 ", used %" PRIu64, + ctx->max_bidi_streams, ctx->used_bidi_streams); + 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 Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *s_data = stream_user_data; + struct h3_stream_ctx *stream; + int rv; + (void)tconn; + (void)max_data; + + rv = nghttp3_conn_unblock_stream(ctx->h3conn, stream_id); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + stream = H3_STREAM_CTX(ctx, s_data); + if(stream && stream->quic_flow_blocked) { + CURL_TRC_CF(s_data, cf, "[%" PRId64 "] unblock quic flow", stream_id); + stream->quic_flow_blocked = FALSE; + Curl_multi_mark_dirty(s_data); + } + 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, fill 0 and call it *random*. */ + memset(dest, 0, destlen); + } +} + +/* for ngtcp2 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; +} + +#ifdef NGTCP2_CALLBACKS_V3 /* ngtcp2 v1.22.0+ */ +static int cb_get_new_connection_id2( + ngtcp2_conn *tconn, ngtcp2_cid *cid, + struct ngtcp2_stateless_reset_token *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->data, sizeof(token->data)); + if(result) + return NGTCP2_ERR_CALLBACK_FAILURE; + + return 0; +} +#endif + +static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_encryption_level level, + void *user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + (void)tconn; + + if(level != NGTCP2_ENCRYPTION_LEVEL_1RTT) + return 0; + + DEBUGASSERT(ctx); + DEBUGASSERT(data); + if(ctx && data && !ctx->h3conn && ctx->init_h3_conn_cb) { + if(ctx->init_h3_conn_cb(cf, data, ctx)) + 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_ngtcp2_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, /* for ngtcp2 user_data : NULL; + ctx = cf ? cf->ctx : NULL; + data = cf ? CF_DATA_CURRENT(cf) : NULL; + if(cf && data && ctx) { + unsigned char *quic_tp = NULL; + size_t quic_tp_len = 0; +#ifdef HAVE_OPENSSL_EARLYDATA + ngtcp2_ssize tplen; + uint8_t tpbuf[256]; + + tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, + sizeof(tpbuf)); + if(tplen < 0) + CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", + ngtcp2_strerror((int)tplen)); + else { + quic_tp = (unsigned char *)tpbuf; + quic_tp_len = (size_t)tplen; + } +#endif + Curl_ossl_add_session(cf, data, ctx->ssl_peer.scache_key, ssl_sessionid, + SSL_version(ssl), "h3", quic_tp, quic_tp_len); + } + return 0; +} +#endif /* USE_OPENSSL */ + +#ifdef USE_GNUTLS + +#ifdef CURLVERBOSE +static const char *gtls_hs_msg_name(int mtype) +{ + switch(mtype) { + case 1: + return "ClientHello"; + case 2: + return "ServerHello"; + case 4: + return "SessionTicket"; + case 8: + return "EncryptedExtensions"; + case 11: + return "Certificate"; + case 13: + return "CertificateRequest"; + case 15: + return "CertificateVerify"; + case 20: + return "Finished"; + case 24: + return "KeyUpdate"; + case 254: + return "MessageHash"; + } + return "Unknown"; +} +#endif + +static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) +{ + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL; + struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; + + (void)msg; + (void)incoming; + if(when && cf && ctx) { /* after message has been processed */ + struct Curl_easy *data = CF_DATA_CURRENT(cf); + DEBUGASSERT(data); + if(!data) + return 0; + CURL_TRC_CF(data, cf, "SSL message: %s %s [%u]", + incoming ? "<-" : "->", gtls_hs_msg_name(htype), htype); + switch(htype) { + case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: { + ngtcp2_ssize tplen; + uint8_t tpbuf[256]; + unsigned char *quic_tp = NULL; + size_t quic_tp_len = 0; + + tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, + sizeof(tpbuf)); + if(tplen < 0) + CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", + ngtcp2_strerror((int)tplen)); + else { + quic_tp = (unsigned char *)tpbuf; + quic_tp_len = (size_t)tplen; + } + (void)Curl_gtls_cache_session(cf, data, ctx->ssl_peer.scache_key, + session, 0, "h3", quic_tp, quic_tp_len); + break; + } + default: + break; + } + } + return 0; +} +#endif /* USE_GNUTLS */ + +#ifdef USE_WOLFSSL +static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) +{ + ngtcp2_crypto_conn_ref *conn_ref = wolfSSL_get_app_data(ssl); + struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL; + + DEBUGASSERT(cf); + if(cf && session) { + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + DEBUGASSERT(data); + if(data && ctx) { + ngtcp2_ssize tplen; + uint8_t tpbuf[256]; + unsigned char *quic_tp = NULL; + size_t quic_tp_len = 0; + + tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, + sizeof(tpbuf)); + if(tplen < 0) + CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", + ngtcp2_strerror((int)tplen)); + else { + quic_tp = (unsigned char *)tpbuf; + quic_tp_len = (size_t)tplen; + } + (void)Curl_wssl_cache_session(cf, data, ctx->ssl_peer.scache_key, + session, wolfSSL_version(ssl), + "h3", quic_tp, quic_tp_len); + } + } + return 0; +} +#endif /* USE_WOLFSSL */ + +static CURLcode cf_ngtcp2_tls_ctx_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data) +{ + struct curl_tls_ctx *ctx = user_data; + +#ifdef USE_OPENSSL +#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL) + if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ossl.ssl_ctx) + != 0) { + failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#elif defined(OPENSSL_QUIC_API2) + /* nothing to do */ +#else + if(ngtcp2_crypto_quictls_configure_client_context(ctx->ossl.ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#endif /* !OPENSSL_IS_AWSLC && !OPENSSL_IS_BORINGSSL */ + if(Curl_ssl_scache_use(cf, data)) { + /* Enable the session cache because it is a prerequisite for the + * "new session" callback. Use the "external storage" mode to prevent + * OpenSSL from creating an internal session cache. + */ + SSL_CTX_set_session_cache_mode(ctx->ossl.ssl_ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ctx->ossl.ssl_ctx, quic_ossl_new_session_cb); + } + +#elif defined(USE_GNUTLS) + if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls.session) != 0) { + failf(data, "ngtcp2_crypto_gnutls_configure_client_session failed"); + return CURLE_FAILED_INIT; + } + if(Curl_ssl_scache_use(cf, data)) { + gnutls_handshake_set_hook_function(ctx->gtls.session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, + quic_gtls_handshake_cb); + } + +#elif defined(USE_WOLFSSL) + if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); + return CURLE_FAILED_INIT; + } + if(Curl_ssl_scache_use(cf, data)) { + /* Register to get notified when a new session is received */ + wolfSSL_CTX_sess_set_new_cb(ctx->wssl.ssl_ctx, wssl_quic_new_session_cb); + } +#endif + return CURLE_OK; +} + +static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct alpn_spec *alpns, + struct Curl_ssl_session *scs, + bool *do_early_data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + *do_early_data = FALSE; +#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) + ctx->earlydata_max = scs->earlydata_max; +#endif +#ifdef USE_GNUTLS + ctx->earlydata_max = + gnutls_record_get_max_early_data_size(ctx->tls.gtls.session); +#endif +#ifdef USE_WOLFSSL +#ifdef WOLFSSL_EARLY_DATA + ctx->earlydata_max = scs->earlydata_max; +#else + ctx->earlydata_max = 0; +#endif /* WOLFSSL_EARLY_DATA */ +#endif +#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ + (defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA)) + if(!ctx->earlydata_max) { + CURL_TRC_CF(data, cf, "SSL session does not allow earlydata"); + } + else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) { + CURL_TRC_CF(data, cf, "SSL session from different ALPN, no early data"); + } + else if(!scs->quic_tp || !scs->quic_tp_len) { + CURL_TRC_CF(data, cf, "no 0RTT transport parameters, no early data"); + } + else { + int rv; + rv = ngtcp2_conn_decode_and_set_0rtt_transport_params( + ctx->qconn, (const uint8_t *)scs->quic_tp, scs->quic_tp_len); + if(rv) + CURL_TRC_CF(data, cf, "no early data, failed to set 0RTT transport " + "parameters: %s", ngtcp2_strerror(rv)); + else if(ctx->init_h3_conn_cb) { + infof(data, "SSL session allows %zu bytes of early data, " + "reusing ALPN '%s'", ctx->earlydata_max, scs->alpn); + result = ctx->init_h3_conn_cb(cf, data, ctx); + if(!result) { + ctx->use_earlydata = TRUE; + cf->connected = TRUE; + *do_early_data = TRUE; + } + } + else { /* h3_conn_init set, assume done */ + ctx->use_earlydata = TRUE; + cf->connected = TRUE; + *do_early_data = TRUE; + } + } +#else /* not supported in the TLS backend */ + (void)data; + (void)ctx; + (void)scs; + (void)alpns; +#endif + return result; +} + +/* + * Might be called twice for happy eyeballs. + */ +static CURLcode cf_connect_start(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rc; + int rv; + CURLcode result; + const struct Curl_sockaddr_ex *sockaddr = NULL; + int qfd; + static const struct alpn_spec ALPN_SPEC_H3 = { { "h3", "h3-29" }, 2 }; + + DEBUGASSERT(ctx->initialized); + ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; + result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN); + if(result) + return result; + + ctx->scid.datalen = NGTCP2_MAX_CIDLEN; + result = Curl_rand(data, ctx->scid.data, NGTCP2_MAX_CIDLEN); + if(result) + return result; + + (void)Curl_qlogdir(data, ctx->scid.data, NGTCP2_MAX_CIDLEN, &qfd); + ctx->qlogfd = qfd; /* -1 if failure above */ + quic_settings(ctx, data, pktx); + + result = vquic_ctx_init(data, &ctx->q); + if(result) + return result; + + /* Query socket and remote address from sub-chain */ + if(Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL)) { + /* No direct socket - must be tunneled QUIC (CONNECT-UDP through proxy) */ + ctx->q.sockfd = CURL_SOCKET_BAD; + } + + if(ctx->q.sockfd != CURL_SOCKET_BAD) { + /* Direct UDP socket - get local address for ngtcp2 */ + ctx->q.local_addrlen = sizeof(ctx->q.local_addr); + rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, + &ctx->q.local_addrlen); + if(rv == -1) + return CURLE_QUIC_CONNECT_ERROR; + + ngtcp2_addr_init(&ctx->connected_path.local, + (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen); + ngtcp2_addr_init(&ctx->connected_path.remote, + &sockaddr->curl_sa_addr, (socklen_t)sockaddr->addrlen); + + rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, + &ctx->connected_path, + NGTCP2_PROTO_VER_V1, &ng_callbacks, + &ctx->settings, &ctx->transport_params, + Curl_ngtcp2_mem(), cf); + if(rc) + return CURLE_QUIC_CONNECT_ERROR; + + ctx->conn_ref.get_conn = get_conn; + ctx->conn_ref.user_data = cf; + } + else { + /* Tunneled QUIC (e.g. CONNECT-UDP): get remote address + from the connected filter below */ + const struct Curl_sockaddr_ex *remote = NULL; + if(cf->next->cft->query(cf->next, data, CF_QUERY_REMOTE_ADDR, NULL, + CURL_UNCONST(&remote))) + return CURLE_QUIC_CONNECT_ERROR; + if(!remote) + return CURLE_QUIC_CONNECT_ERROR; + + memset(&ctx->q.local_addr, 0, sizeof(ctx->q.local_addr)); + switch(remote->family) { + case AF_INET: + ((struct sockaddr_in *)&ctx->q.local_addr)->sin_family = AF_INET; + ctx->q.local_addrlen = sizeof(struct sockaddr_in); + break; +#ifdef USE_IPV6 + case AF_INET6: + ((struct sockaddr_in6 *)&ctx->q.local_addr)->sin6_family = AF_INET6; + ctx->q.local_addrlen = sizeof(struct sockaddr_in6); + break; +#endif + default: + return CURLE_QUIC_CONNECT_ERROR; + } + + ngtcp2_addr_init(&ctx->connected_path.local, + (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen); + ngtcp2_addr_init(&ctx->connected_path.remote, + &remote->curl_sa_addr, + (socklen_t)remote->addrlen); + + rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, + &ctx->connected_path, + NGTCP2_PROTO_VER_V1, &ng_callbacks, + &ctx->settings, &ctx->transport_params, + Curl_ngtcp2_mem(), cf); + if(rc) + return CURLE_QUIC_CONNECT_ERROR; + + ctx->conn_ref.get_conn = get_conn; + ctx->conn_ref.user_data = cf; + } + + result = Curl_vquic_tls_init(&ctx->tls, cf, data, + &ctx->ssl_peer, &ALPN_SPEC_H3, + cf_ngtcp2_tls_ctx_setup, &ctx->tls, + &ctx->conn_ref, + cf_ngtcp2_on_session_reuse); + if(result) + return result; + +#if defined(USE_OPENSSL) && defined(OPENSSL_QUIC_API2) + if(ngtcp2_crypto_ossl_ctx_new(&ctx->ossl_ctx, ctx->tls.ossl.ssl) != 0) { + failf(data, "ngtcp2_crypto_ossl_ctx_new failed"); + return CURLE_FAILED_INIT; + } + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ossl_ctx); + if(ngtcp2_crypto_ossl_configure_client_session(ctx->tls.ossl.ssl) != 0) { + failf(data, "ngtcp2_crypto_ossl_configure_client_session failed"); + return CURLE_FAILED_INIT; + } +#elif defined(USE_OPENSSL) + SSL_set_quic_use_legacy_codepoint(ctx->tls.ossl.ssl, 0); + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ossl.ssl); +#elif defined(USE_GNUTLS) + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls.session); +#elif defined(USE_WOLFSSL) + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.wssl.ssl); +#else +#error "ngtcp2 TLS backend not defined" +#endif + + ngtcp2_ccerr_default(&ctx->last_error); + + return CURLE_OK; +} + +CURLcode Curl_cf_ngtcp2_cmn_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + struct cf_ngtcp2_io_ctx pktx; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* Connect the sub-chain */ + if(cf->next && !cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, done); + if(result || !*done) + return result; + } + + *done = FALSE; + + if(cf_ngtcp2_need_httpsrr(data) && + !Curl_conn_dns_resolved_https(data, cf->sockindex)) { + CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); + return CURLE_OK; + } + + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); + CF_DATA_SAVE(save, cf, data); + + if(!ctx->qconn) { + ctx->started_at = *Curl_pgrs_now(data); + result = cf_connect_start(cf, data, &pktx); + if(result) + goto out; + if(cf->connected) { + *done = TRUE; + goto out; + } + result = Curl_cf_ngtcp2_progress_egress(cf, data, &pktx); + /* we do not expect to be able to recv anything yet */ + goto out; + } + + result = Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx); + if(result) + goto out; + + result = Curl_cf_ngtcp2_progress_egress(cf, data, &pktx); + if(result) + goto out; + + if(ngtcp2_conn_get_handshake_completed(ctx->qconn)) { + result = ctx->tls_vrfy_result; + if(!result) { + CURL_TRC_CF(data, cf, "peer verified"); + cf->connected = TRUE; + *done = TRUE; + } + } + +out: + if(ctx->tls_vrfy_result) + result = ctx->tls_vrfy_result; + if(ctx->qconn && + ((result == CURLE_RECV_ERROR) || (result == CURLE_SEND_ERROR)) && + ngtcp2_conn_in_draining_period(ctx->qconn)) { + const ngtcp2_ccerr *cerr = ngtcp2_conn_get_ccerr(ctx->qconn); + + result = CURLE_COULDNT_CONNECT; + if(cerr) { + CURL_TRC_CF(data, cf, "connect error, type=%d, code=%" PRIu64, + cerr->type, cerr->error_code); + switch(cerr->type) { + case NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION: + CURL_TRC_CF(data, cf, "error in version negotiation"); + break; + default: + if(cerr->error_code >= NGTCP2_CRYPTO_ERROR) { + CURL_TRC_CF(data, cf, "crypto error, tls alert=%u", + (unsigned int)(cerr->error_code & 0xffU)); + } + else if(cerr->error_code == NGTCP2_CONNECTION_REFUSED) { + CURL_TRC_CF(data, cf, "connection refused by server"); + /* When a QUIC server instance is shutting down, it may send us a + * CONNECTION_CLOSE with this code right away. We want + * to keep on trying in this case. */ + result = CURLE_WEIRD_SERVER_REPLY; + } + } + } + } + +#ifdef CURLVERBOSE + if(result) { + if(ctx->q.sockfd != CURL_SOCKET_BAD) { + /* Direct UDP socket - get IP info for error reporting */ + struct ip_quadruple ip; + + if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) + infof(data, "QUIC connect to %s port %u failed: %s", + ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); + } + } +#endif + if(!result && ctx->qconn) { + result = Curl_cf_ngtcp2_cmn_set_expiry(cf, data, &pktx); + } + if(result || *done) + CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); + CF_DATA_RESTORE(cf, save); + return result; +} + +CURLcode Curl_cf_ngtcp2_cmn_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_call_data save; + struct cf_ngtcp2_io_ctx pktx; + CURLcode result = CURLE_OK; + + if(cf->shutdown || !ctx->qconn) { + *done = TRUE; + return CURLE_OK; + } + + if(!cf->next) { + Curl_bufq_reset(&ctx->q.sendbuf); + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + *done = FALSE; + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); + + if(!ctx->shutdown_started) { + char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; + ngtcp2_ssize nwritten; + + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing sendbuf"); + result = Curl_cf_ngtcp2_progress_egress(cf, data, &pktx); + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); + result = CURLE_OK; + goto out; + } + else if(result) { + CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); + *done = TRUE; + goto out; + } + } + + DEBUGASSERT(Curl_bufq_is_empty(&ctx->q.sendbuf)); + ctx->shutdown_started = TRUE; + nwritten = ngtcp2_conn_write_connection_close( + ctx->qconn, NULL, /* path */ + NULL, /* pkt_info */ + (uint8_t *)buffer, sizeof(buffer), + &ctx->last_error, pktx.ts); + CURL_TRC_CF(data, cf, "start shutdown(err_type=%d, err_code=%" + PRIu64 ") -> %zd", ctx->last_error.type, + ctx->last_error.error_code, (ssize_t)nwritten); + /* there are cases listed in ngtcp2 documentation where this call + * may fail. Since we are doing a connection shutdown as graceful + * as we can, such an error is ignored here. */ + if(nwritten > 0) { + /* Ignore amount written. sendbuf was empty and has always room for + * NGTCP2_MAX_UDP_PAYLOAD_SIZE. It can only completely fail, in which + * case `result` is set non zero. */ + size_t n; + result = Curl_bufq_write(&ctx->q.sendbuf, (const unsigned char *)buffer, + (size_t)nwritten, &n); + if(result) { + CURL_TRC_CF(data, cf, "error %d adding shutdown packets to sendbuf, " + "aborting shutdown", result); + goto out; + } + + ctx->q.no_gso = TRUE; + ctx->q.gsolen = (size_t)nwritten; + ctx->q.split_len = 0; + } + } + + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing egress"); + result = vquic_flush(cf, data, &ctx->q); + if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); + result = CURLE_OK; + goto out; + } + else if(result) { + CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); + *done = TRUE; + goto out; + } + } + + if(Curl_bufq_is_empty(&ctx->q.sendbuf)) { + /* Sent everything off. ngtcp2 seems to have no support for graceful + * shutdowns. We are done. */ + CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); + *done = TRUE; + result = CURLE_OK; + } +out: + CF_DATA_RESTORE(cf, save); + return result; +} + +void Curl_cf_ngtcp2_cmn_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + bool done; + Curl_cf_ngtcp2_cmn_shutdown(cf, data, &done); +} + +static bool cf_ngtcp2_err_is_fatal(int code) +{ + return (NGTCP2_ERR_FATAL >= code) || + (NGTCP2_ERR_DROP_CONN == code) || + (NGTCP2_ERR_IDLE_CLOSE == code); +} + +void Curl_cf_ngtcp2_cmn_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + if(!ctx->last_error.error_code) { + if(NGTCP2_ERR_CRYPTO == code) { + ngtcp2_ccerr_set_tls_alert(&ctx->last_error, + ngtcp2_conn_get_tls_alert(ctx->qconn), + NULL, 0); + } + else { + ngtcp2_ccerr_set_liberr(&ctx->last_error, code, NULL, 0); + } + } + if(cf_ngtcp2_err_is_fatal(code)) + Curl_cf_ngtcp2_cmn_conn_close(cf, data); +} + +void Curl_cf_ngtcp2_io_ctx_init(struct cf_ngtcp2_io_ctx *io_ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + const struct curltime *pnow = Curl_pgrs_now(data); + + io_ctx->cf = cf; + io_ctx->data = data; + ngtcp2_path_storage_zero(&io_ctx->ps); + vquic_ctx_set_time(&ctx->q, pnow); + io_ctx->ts = ((ngtcp2_tstamp)pnow->tv_sec * NGTCP2_SECONDS) + + ((ngtcp2_tstamp)pnow->tv_usec * NGTCP2_MICROSECONDS); +} + +void Curl_cf_ngtcp2_io_ctx_update_time(struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx, + struct Curl_cfilter *cf) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + const struct curltime *pnow = Curl_pgrs_now(data); + + vquic_ctx_update_time(&ctx->q, pnow); + pktx->ts = ((ngtcp2_tstamp)pnow->tv_sec * NGTCP2_SECONDS) + + ((ngtcp2_tstamp)pnow->tv_usec * NGTCP2_MICROSECONDS); +} + +#if NGTCP2_VERSION_NUM < 0x011100 +struct cf_ngtcp2_sfind_ctx { + int64_t stream_id; + struct h3_stream_ctx *stream; + uint32_t mid; +}; + +static bool cf_ngtcp2_sfind(uint32_t mid, void *value, void *user_data) +{ + struct cf_ngtcp2_sfind_ctx *fctx = user_data; + struct h3_stream_ctx *stream = value; + + if(fctx->stream_id == stream->id) { + fctx->mid = mid; + fctx->stream = stream; + return FALSE; + } + return TRUE; /* continue */ +} + +static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, + int64_t stream_id) +{ + struct cf_ngtcp2_sfind_ctx fctx; + fctx.stream_id = stream_id; + fctx.stream = NULL; + Curl_uint32_hash_visit(&ctx->streams, cf_ngtcp2_sfind, &fctx); + return fctx.stream; +} +#else +static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, + int64_t stream_id) +{ + struct Curl_easy *data = + ngtcp2_conn_get_stream_user_data(ctx->qconn, stream_id); + + if(!data) { + return NULL; + } + + return H3_STREAM_CTX(ctx, data); +} +#endif + +/** + * Read a network packet to send from ngtcp2 into `buf`. + * Return number of bytes written or -1 with *err set. + */ +static CURLcode read_pkt_to_send(void *userp, + unsigned char *buf, size_t buflen, + size_t *pnread) +{ + struct cf_ngtcp2_io_ctx *x = userp; + struct cf_ngtcp2_ctx *ctx = x->cf->ctx; + nghttp3_vec vec[16]; + nghttp3_ssize veccnt; + ngtcp2_ssize ndatalen; + uint32_t flags; + int64_t stream_id; + int fin; + ssize_t n; + + *pnread = 0; + veccnt = 0; + stream_id = -1; + fin = 0; + + /* ngtcp2 may want to put several frames from different streams into + * this packet. `NGTCP2_WRITE_STREAM_FLAG_MORE` tells it to do so. + * When `NGTCP2_ERR_WRITE_MORE` is returned, we *need* to make + * another iteration. + * When ngtcp2 is happy (because it has no other frame that would fit + * or it has nothing more to send), it returns the total length + * of the assembled packet. This may be 0 if there was nothing to send. */ + for(;;) { + + if(ctx->h3conn && ngtcp2_conn_get_max_data_left(ctx->qconn)) { + veccnt = nghttp3_conn_writev_stream(ctx->h3conn, &stream_id, &fin, vec, + CURL_ARRAYSIZE(vec)); + if(veccnt < 0) { + failf(x->data, "nghttp3_conn_writev_stream returned error: %s", + nghttp3_strerror((int)veccnt)); + Curl_cf_ngtcp2_h3_err_set(x->cf, x->data, (int)veccnt); + return CURLE_SEND_ERROR; + } + } + + flags = NGTCP2_WRITE_STREAM_FLAG_MORE | + (fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0); + n = ngtcp2_conn_writev_stream(ctx->qconn, &x->ps.path, + NULL, buf, buflen, + &ndatalen, flags, stream_id, + (const ngtcp2_vec *)vec, veccnt, x->ts); + if(n == 0) { + /* nothing to send */ + return CURLE_AGAIN; + } + else if(n < 0) { + switch(n) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: { + struct h3_stream_ctx *stream; + DEBUGASSERT(ndatalen == -1); + nghttp3_conn_block_stream(ctx->h3conn, stream_id); + CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] block quic flow", + stream_id); + stream = cf_ngtcp2_get_stream(ctx, stream_id); + if(stream) /* it might be not one of our h3 streams? */ + stream->quic_flow_blocked = TRUE; + n = 0; + break; + } + case NGTCP2_ERR_STREAM_SHUT_WR: + DEBUGASSERT(ndatalen == -1); + nghttp3_conn_shutdown_stream_write(ctx->h3conn, stream_id); + n = 0; + break; + case NGTCP2_ERR_WRITE_MORE: + /* ngtcp2 wants to send more. update the flow of the stream whose data + * is in the buffer and continue */ + DEBUGASSERT(ndatalen >= 0); + n = 0; + break; + default: + DEBUGASSERT(ndatalen == -1); + failf(x->data, "ngtcp2_conn_writev_stream returned error: %s", + ngtcp2_strerror((int)n)); + Curl_cf_ngtcp2_cmn_err_set(x->cf, x->data, (int)n); + return CURLE_SEND_ERROR; + } + } + + if(ndatalen >= 0) { + /* we add the amount of data bytes to the flow windows */ + int rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen); + if(rv) { + failf(x->data, "nghttp3_conn_add_write_offset returned error: %s", + nghttp3_strerror(rv)); + return CURLE_SEND_ERROR; + } + } + + if(n > 0) { + /* packet assembled, leave */ + *pnread = (size_t)n; + return CURLE_OK; + } + } +} + +CURLcode Curl_cf_ngtcp2_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + size_t nread; + size_t max_payload_size, path_max_payload_size; + size_t pktcnt = 0; + size_t gsolen = 0; /* this disables gso until we have a clue */ + size_t send_quantum; + CURLcode result; + struct cf_ngtcp2_io_ctx local_pktx; + + if(!pktx) { + Curl_cf_ngtcp2_io_ctx_init(&local_pktx, cf, data); + pktx = &local_pktx; + } + else { + Curl_cf_ngtcp2_io_ctx_update_time(data, pktx, cf); + ngtcp2_path_storage_zero(&pktx->ps); + } + + result = vquic_flush(cf, data, &ctx->q); + if(result) { + if(result == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return result; + } + + /* In UDP, there is a maximum theoretical packet payload length and + * a minimum payload length that is "guaranteed" to work. + * To detect if this minimum payload can be increased, ngtcp2 sends + * now and then a packet payload larger than the minimum. It that + * is ACKed by the peer, both parties know that it works and + * the subsequent packets can use a larger one. + * This is called PMTUD (Path Maximum Transmission Unit Discovery). + * Since a PMTUD might be rejected right on send, we do not want it + * be followed by other packets of lesser size. Because those would + * also fail then. If we detect a PMTUD while buffering, we flush. + */ + max_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(ctx->qconn); + path_max_payload_size = + ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn); + send_quantum = ngtcp2_conn_get_send_quantum(ctx->qconn); + CURL_TRC_CF(data, cf, "egress, collect and send packets, quantum=%zu", + send_quantum); + for(;;) { + /* add the next packet to send, if any, to our buffer */ + result = Curl_bufq_sipn(&ctx->q.sendbuf, max_payload_size, + read_pkt_to_send, pktx, &nread); + if(result == CURLE_AGAIN) + break; + else if(result) + return result; + else { + size_t buflen = Curl_bufq_len(&ctx->q.sendbuf); + if((buflen >= send_quantum) || + ((buflen + gsolen) >= ctx->q.sendbuf.chunk_size)) + break; + DEBUGASSERT(nread > 0); + ++pktcnt; + if(pktcnt == 1) { + /* first packet in buffer. This is either of a known, "good" + * payload size or it is a PMTUD. We shall see. */ + gsolen = nread; + } + else if(nread > gsolen || + (gsolen > path_max_payload_size && nread != gsolen)) { + /* The added packet is a PMTUD *or* the one(s) before the + * added were PMTUD and the last one is smaller. + * Flush the buffer before the last add. */ + result = vquic_send_tail_split(cf, data, &ctx->q, + gsolen, nread, nread); + if(result) { + if(result == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return result; + } + pktcnt = 0; + } + else if(nread < gsolen) { + /* Reached capacity of our buffer *or* + * last add was shorter than the previous ones, flush */ + break; + } + } + } + + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + /* time to send */ + CURL_TRC_CF(data, cf, "egress, send collected %zu packets in %zu bytes", + pktcnt, Curl_bufq_len(&ctx->q.sendbuf)); + result = vquic_send(cf, data, &ctx->q, gsolen); + if(result) { + if(result == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return result; + } + Curl_cf_ngtcp2_io_ctx_update_time(data, pktx, cf); + ngtcp2_conn_update_pkt_tx_time(ctx->qconn, pktx->ts); + } + return CURLE_OK; +} + +struct cf_ngtcp2_recv_ctx { + struct cf_ngtcp2_io_ctx *pktx; + size_t pkt_count; +}; + +static CURLcode cf_ngtcp2_recv_pkts(const unsigned char *buf, size_t buflen, + size_t gso_size, + struct sockaddr_storage *remote_addr, + socklen_t remote_addrlen, int ecn, + void *userp) +{ + struct cf_ngtcp2_recv_ctx *rctx = userp; + struct cf_ngtcp2_io_ctx *pktx = rctx->pktx; + struct cf_ngtcp2_ctx *ctx = pktx->cf->ctx; + ngtcp2_pkt_info pi; + ngtcp2_path path; + size_t offset, pktlen; + int rv; + + if(!rctx->pkt_count) { + Curl_cf_ngtcp2_io_ctx_update_time(pktx->data, pktx, pktx->cf); + ngtcp2_path_storage_zero(&pktx->ps); + } + + if(ecn) + CURL_TRC_CF(pktx->data, pktx->cf, "vquic_recv(len=%zu, gso=%zu, ecn=%x)", + buflen, gso_size, ecn); + ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen); + ngtcp2_addr_init(&path.remote, (struct sockaddr *)remote_addr, + remote_addrlen); + pi.ecn = (uint8_t)ecn; + + for(offset = 0; offset < buflen; offset += gso_size) { + rctx->pkt_count++; + pktlen = ((offset + gso_size) <= buflen) ? gso_size : (buflen - offset); + rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, + buf + offset, pktlen, pktx->ts); + if(rv) { + CURL_TRC_CF(pktx->data, pktx->cf, "ingress, read_pkt -> %s (%d)", + ngtcp2_strerror(rv), rv); + Curl_cf_ngtcp2_cmn_err_set(pktx->cf, pktx->data, rv); + + 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; +} + +CURLcode Curl_cf_ngtcp2_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_ngtcp2_io_ctx local_pktx; + struct cf_ngtcp2_recv_ctx rctx; + CURLcode result = CURLE_OK; + + if(!pktx) { + Curl_cf_ngtcp2_io_ctx_init(&local_pktx, cf, data); + pktx = &local_pktx; + } + + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + if(result) + return result; + + rctx.pktx = pktx; + rctx.pkt_count = 0; + + if(ctx->q.sockfd != CURL_SOCKET_BAD) { + /* Direct UDP socket (via happy eyeballs) */ + CURL_TRC_CF(data, cf, "progress_ingress(socket)"); + return vquic_recv_packets(cf, data, &ctx->q, 1000, + cf_ngtcp2_recv_pkts, &rctx); + } + else { + /* Tunneled QUIC (CONNECT-UDP through proxy) */ + unsigned char *buf; + size_t max_udp_payload = QUIC_TUNNEL_INBUF_SIZE; + size_t pkt_limit = QUIC_TUNNEL_INGRESS_PKT_LIMIT; + size_t nread; + struct sockaddr_storage remote_addr; + socklen_t remote_addrlen; + + CURL_TRC_CF(data, cf, "progress_ingress(sub-filters)"); + if(ctx->qconn) { + size_t max_path_payload; + max_path_payload = + ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn); + if(max_path_payload > max_udp_payload) + max_udp_payload = max_path_payload; + } + + if(ctx->tunnel_inbuf_len < max_udp_payload) { + unsigned char *newbuf = curlx_realloc(ctx->tunnel_inbuf, + max_udp_payload); + if(!newbuf) + return CURLE_OUT_OF_MEMORY; + ctx->tunnel_inbuf = newbuf; + ctx->tunnel_inbuf_len = max_udp_payload; + } + buf = ctx->tunnel_inbuf; + + while(pkt_limit--) { + result = Curl_conn_cf_recv(cf->next, data, (char *)buf, + ctx->tunnel_inbuf_len, &nread); + if(result == CURLE_AGAIN) { + /* no more data available at the moment */ + return CURLE_OK; + } + if(result) { + CURL_TRC_CF(data, cf, "ingress, recv from tunnel failed: %d", result); + return result; + } + if(nread == 0) { + /* tunnel closed */ + return CURLE_OK; + } + + memcpy(&remote_addr, ctx->connected_path.remote.addr, + ctx->connected_path.remote.addrlen); + remote_addrlen = (socklen_t)ctx->connected_path.remote.addrlen; + result = cf_ngtcp2_recv_pkts(buf, nread, nread, &remote_addr, + remote_addrlen, 0, &rctx); + if(result) + return result; + + if(!ctx->q.got_first_byte) { + ctx->q.got_first_byte = TRUE; + ctx->q.first_byte_at = ctx->q.last_op; + } + ctx->q.last_io = ctx->q.last_op; + } + return CURLE_OK; + } +} + +/** + * Connection maintenance like timeouts on packet ACKs etc. are done by us, not + * the OS like for TCP. POLL events on the socket therefore are not + * sufficient. + * ngtcp2 tells us when it wants to be invoked again. We handle that via + * the `Curl_expire()` mechanisms. + */ +CURLcode Curl_cf_ngtcp2_cmn_set_expiry(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_ngtcp2_io_ctx local_pktx; + ngtcp2_tstamp expiry; + + if(!pktx) { + Curl_cf_ngtcp2_io_ctx_init(&local_pktx, cf, data); + pktx = &local_pktx; + } + else { + Curl_cf_ngtcp2_io_ctx_update_time(data, pktx, cf); + } + + expiry = ngtcp2_conn_get_expiry(ctx->qconn); + if(expiry != UINT64_MAX) { + if(expiry <= pktx->ts) { + CURLcode result; + int rv = ngtcp2_conn_handle_expiry(ctx->qconn, pktx->ts); + if(rv) { + failf(data, "ngtcp2_conn_handle_expiry returned error: %s", + ngtcp2_strerror(rv)); + Curl_cf_ngtcp2_cmn_err_set(cf, data, rv); + return CURLE_SEND_ERROR; + } + result = Curl_cf_ngtcp2_progress_ingress(cf, data, pktx); + if(result) + return result; + result = Curl_cf_ngtcp2_progress_egress(cf, data, pktx); + if(result) + return result; + /* ask again, things might have changed */ + expiry = ngtcp2_conn_get_expiry(ctx->qconn); + } + + if(expiry > pktx->ts) { + ngtcp2_duration timeout = expiry - pktx->ts; + if(timeout % NGTCP2_MILLISECONDS) { + timeout += NGTCP2_MILLISECONDS; + } + Curl_expire(data, (timediff_t)(timeout / NGTCP2_MILLISECONDS), + EXPIRE_QUIC); + } + } + return CURLE_OK; +} + +static void cf_ngtcp2_setup_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + const ngtcp2_transport_params *rp; + /* Peer should have sent us its transport parameters. If it + * announces a positive `max_idle_timeout` it closes the + * connection when it does not hear from us for that time. + * + * Some servers use this as a keep-alive timer at a rather low + * value. We are doing HTTP/3 here and waiting for the response + * to a request may take a considerable amount of time. We need + * to prevent the peer's QUIC stack from closing in this case. + */ + if(!ctx->qconn) + return; + + rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); + if(!rp || !rp->max_idle_timeout) { + ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); + CURL_TRC_CF(data, cf, "no peer idle timeout, unset keep-alive"); + } + else if(!Curl_uint32_hash_count(&ctx->streams)) { + ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); + CURL_TRC_CF(data, cf, "no active streams, unset keep-alive"); + } + else { + ngtcp2_duration keep_ns; + keep_ns = (rp->max_idle_timeout > 1) ? (rp->max_idle_timeout / 2) : 1; + ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, keep_ns); + CURL_TRC_CF(data, cf, "peer idle timeout is %" PRIu64 "ms, " + "set keep-alive to %" PRIu64 " ms.", + (rp->max_idle_timeout / NGTCP2_MILLISECONDS), + (keep_ns / NGTCP2_MILLISECONDS)); + } +} + +CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + if(!data) + return CURLE_FAILED_INIT; + + if(stream) + return CURLE_OK; + + stream = curlx_calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + stream->id = -1; + stream->rx_offset = 0; + stream->rx_offset_max = H3_STREAM_WINDOW_SIZE_INITIAL; + + /* on send, we control how much we put into the buffer */ + Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, + H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); + stream->sendbuf_len_in_flight = 0; + stream->window_size_max = H3_STREAM_WINDOW_SIZE_INITIAL; + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + + if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { + Curl_cf_ngtcp2_h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + + if(Curl_uint32_hash_count(&ctx->streams) == 1) + cf_ngtcp2_setup_keep_alive(cf, data); + + return CURLE_OK; +} + +void Curl_cf_ngtcp2_h3_stream_close(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + DEBUGASSERT(data); + DEBUGASSERT(stream); + if(!stream->closed && ctx->qconn && ctx->h3conn) { + CURLcode result; + + nghttp3_conn_set_stream_user_data(ctx->h3conn, stream->id, NULL); + ngtcp2_conn_set_stream_user_data(ctx->qconn, stream->id, NULL); + stream->closed = TRUE; + (void)ngtcp2_conn_shutdown_stream(ctx->qconn, 0, stream->id, + NGHTTP3_H3_REQUEST_CANCELLED); + result = Curl_cf_ngtcp2_progress_egress(cf, data, NULL); + if(result) + CURL_TRC_CF(data, cf, "[%" PRId64 "] cancel stream -> %d", + stream->id, result); + } +} + +void Curl_cf_ngtcp2_h3_stream_done(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] easy handle is done", stream->id); + Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); + Curl_uint32_hash_remove(&ctx->streams, data->mid); + if(!Curl_uint32_hash_count(&ctx->streams)) + cf_ngtcp2_setup_keep_alive(cf, data); + } +} + +bool Curl_cf_ngtcp2_cmn_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + bool alive = FALSE; + const ngtcp2_transport_params *rp; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + *input_pending = FALSE; + if(!ctx->qconn || ctx->shutdown_started) + goto out; + + /* We do not announce a max idle timeout, but when the peer does + * it closes the connection when it expires. */ + rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); + if(rp && rp->max_idle_timeout) { + timediff_t idletime_ms = + curlx_ptimediff_ms(Curl_pgrs_now(data), &ctx->q.last_io); + if(idletime_ms > 0) { + uint64_t max_idle_ms = + (uint64_t)(rp->max_idle_timeout / NGTCP2_MILLISECONDS); + if((uint64_t)idletime_ms > max_idle_ms) + goto out; + } + } + + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) + goto out; + + alive = TRUE; + if(*input_pending) { + CURLcode result; + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, + only "protocol frames" */ + *input_pending = FALSE; + result = Curl_cf_ngtcp2_progress_ingress(cf, data, NULL); + CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); + alive = result ? FALSE : TRUE; + } + +out: + CF_DATA_RESTORE(cf, save); + return alive; +} + +CURLcode Curl_cf_ngtcp2_h3_init_ctrls(struct cf_ngtcp2_ctx *ctx, + struct Curl_easy *data) +{ + int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id; + int rc; + + rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL); + if(rc) { + failf(data, "error creating HTTP/3 control stream: %s", + ngtcp2_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; + } + rc = nghttp3_conn_bind_control_stream(ctx->h3conn, ctrl_stream_id); + if(rc) { + failf(data, "error binding HTTP/3 control stream: %s", + ngtcp2_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; + } + rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_enc_stream_id, NULL); + if(rc) { + failf(data, "error creating HTTP/3 qpack encoding stream: %s", + ngtcp2_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; + } + rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_dec_stream_id, NULL); + if(rc) { + failf(data, "error creating HTTP/3 qpack decoding stream: %s", + ngtcp2_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; + } + rc = nghttp3_conn_bind_qpack_streams(ctx->h3conn, qpack_enc_stream_id, + qpack_dec_stream_id); + if(rc) { + failf(data, "error binding HTTP/3 qpack streams: %s", ngtcp2_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; + } + return CURLE_OK; +} + +#endif /* !CURL_DISABLE_HTTP && USE_NGTCP2 && USE_NGHTTP3 */ diff --git a/lib/vquic/cf-ngtcp2-cmn.h b/lib/vquic/cf-ngtcp2-cmn.h new file mode 100644 index 0000000000..88554edfb6 --- /dev/null +++ b/lib/vquic/cf-ngtcp2-cmn.h @@ -0,0 +1,239 @@ +#ifndef HEADER_CURL_VQUIC_CF_NGTCP2_CMN_H +#define HEADER_CURL_VQUIC_CF_NGTCP2_CMN_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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" + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_NGTCP2) && defined(USE_NGHTTP3) + +#include +#include + +#ifdef USE_OPENSSL +#include +#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL) +#include +#elif defined(OPENSSL_QUIC_API2) +#include +#else +#include +#endif +#include "vtls/openssl.h" +#elif defined(USE_GNUTLS) +#include +#include "vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include +#include +#include +#include +#include "vtls/wolfssl.h" +#endif + +#ifdef HAVE_NETINET_UDP_H +#include +#endif + +#include + +#include "http1.h" +#include "uint-hash.h" +#include "vtls/vtls.h" +#include "vquic/vquic_int.h" +#include "vquic/vquic-tls.h" + +struct Curl_cfilter; +struct Curl_easy; +struct cf_ngtcp2_ctx; +struct cf_quic_ctx; + +#define QUIC_MAX_STREAMS (256 * 1024) +#define QUIC_HANDSHAKE_TIMEOUT (10 * NGTCP2_SECONDS) +#define QUIC_TUNNEL_INBUF_SIZE (64 * 1024) + +/* We announce a small window size in transport param to the server, + * and grow that immediately to max when no rate limit is in place. + * We need to start small as we are not able to decrease it. */ +#define H3_STREAM_WINDOW_SIZE_INITIAL (32 * 1024) +#define H3_STREAM_WINDOW_SIZE_MAX (10 * 1024 * 1024) +#define H3_CONN_WINDOW_SIZE_MAX (100 * H3_STREAM_WINDOW_SIZE_MAX) + +#define H3_STREAM_CHUNK_SIZE (64 * 1024) +#if H3_STREAM_CHUNK_SIZE < NGTCP2_MAX_UDP_PAYLOAD_SIZE +#error H3_STREAM_CHUNK_SIZE smaller than NGTCP2_MAX_UDP_PAYLOAD_SIZE +#endif +/* The pool keeps spares around and half of a full stream window + * seems good. More does not seem to improve performance. + * The benefit of the pool is that stream buffers do not keep + * spares. Memory consumption goes down when streams run empty, + * have a large upload done, etc. */ +#define H3_STREAM_POOL_SPARES 2 +/* The max amount of un-acked upload data we keep around per stream */ +#define H3_STREAM_SEND_BUFFER_MAX (10 * 1024 * 1024) +#define H3_STREAM_SEND_CHUNKS \ + (H3_STREAM_SEND_BUFFER_MAX / H3_STREAM_CHUNK_SIZE) +#define QUIC_TUNNEL_INGRESS_PKT_LIMIT 1000 + + +void Curl_ngtcp2_ver(char *p, size_t len); + +typedef CURLcode cf_ngtcp2_init_h3_conn(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_ctx *ctx); + +struct cf_ngtcp2_ctx { + struct cf_quic_ctx q; + struct ssl_peer ssl_peer; + struct curl_tls_ctx tls; +#ifdef OPENSSL_QUIC_API2 + ngtcp2_crypto_ossl_ctx *ossl_ctx; +#endif + ngtcp2_path connected_path; + ngtcp2_conn *qconn; + ngtcp2_cid dcid; + ngtcp2_cid scid; + uint32_t version; + ngtcp2_settings settings; + ngtcp2_transport_params transport_params; + ngtcp2_ccerr last_error; + ngtcp2_crypto_conn_ref conn_ref; + struct cf_call_data call_data; + cf_ngtcp2_init_h3_conn *init_h3_conn_cb; + nghttp3_conn *h3conn; + nghttp3_settings h3settings; + struct curltime started_at; /* time the current attempt started */ + struct curltime handshake_at; /* time connect handshake finished */ + struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct dynbuf scratch; /* temp buffer for header construction */ + struct uint_hash streams; /* hash data->mid to h3_stream_ctx */ + uint64_t used_bidi_streams; /* bidi streams we have opened */ + uint64_t max_bidi_streams; /* max bidi streams we can open */ + size_t earlydata_max; /* max amount of early data supported by + server on session reuse */ + size_t earlydata_skip; /* sending bytes to skip when earlydata + is accepted by peer */ + CURLcode tls_vrfy_result; /* result of TLS peer verification */ + int qlogfd; + unsigned char *tunnel_inbuf; /* ingress buffer for tunneled packets */ + size_t tunnel_inbuf_len; + BIT(initialized); + BIT(tls_handshake_complete); /* TLS handshake is done */ + BIT(use_earlydata); /* Using 0RTT data */ + BIT(earlydata_accepted); /* 0RTT was accepted by server */ + BIT(shutdown_started); /* queued shutdown packets */ +}; + +/* How to access `call_data` from a cf_ngtcp2 filter */ +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) ((struct cf_ngtcp2_ctx *)(cf)->ctx)->call_data + +CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, + cf_ngtcp2_init_h3_conn *init_h3_conn_cb); +void Curl_cf_ngtcp2_ctx_cleanup(struct cf_ngtcp2_ctx *ctx); +void Curl_cf_ngtcp2_cmn_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code); + +/** + * All about the H3 internals of a stream + */ +struct h3_stream_ctx { + int64_t id; /* HTTP/3 stream identifier */ + struct bufq sendbuf; /* h3 request body */ + struct h1_req_parser h1; /* h1 request parsing */ + size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ + uint64_t error3; /* HTTP/3 stream error code */ + curl_off_t upload_left; /* number of request bytes left to upload */ + curl_off_t rx_total; /* total number of bytes received */ + uint64_t rx_offset; /* current receive offset */ + uint64_t rx_offset_max; /* allowed receive offset */ + uint64_t window_size_max; /* max flow control window set for stream */ + int status_code; /* HTTP status code */ + CURLcode xfer_result; /* result from xfer_resp_write(_hd) */ + BIT(resp_hds_complete); /* we have a complete, final response */ + BIT(closed); /* TRUE on stream close */ + BIT(reset); /* TRUE on stream reset */ + BIT(send_closed); /* stream is local closed */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ +}; + +void Curl_cf_ngtcp2_h3_stream_ctx_free(struct h3_stream_ctx *stream); +void Curl_cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code); + +CURLcode Curl_cf_ngtcp2_h3_init_ctrls(struct cf_ngtcp2_ctx *ctx, + struct Curl_easy *data); + +CURLcode Curl_cf_ngtcp2_cmn_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done); + +CURLcode Curl_cf_ngtcp2_cmn_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done); +void Curl_cf_ngtcp2_cmn_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data); + +struct cf_ngtcp2_io_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; + ngtcp2_tstamp ts; + ngtcp2_path_storage ps; +}; + +void Curl_cf_ngtcp2_io_ctx_init(struct cf_ngtcp2_io_ctx *io_ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data); +void Curl_cf_ngtcp2_io_ctx_update_time(struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx, + struct Curl_cfilter *cf); + +CURLcode Curl_cf_ngtcp2_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx); + +CURLcode Curl_cf_ngtcp2_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx); + +CURLcode Curl_cf_ngtcp2_cmn_set_expiry(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_io_ctx *pktx); + +CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, + struct Curl_easy *data); +void Curl_cf_ngtcp2_h3_stream_close(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream); +void Curl_cf_ngtcp2_h3_stream_done(struct Curl_cfilter *cf, + struct Curl_easy *data); + +bool Curl_cf_ngtcp2_cmn_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending); + +#endif /* !CURL_DISABLE_HTTP && USE_NGTCP2 && USE_NGHTTP3 */ + +#endif /* HEADER_CURL_VQUIC_CF_NGTCP2_CMN_H */ diff --git a/lib/vquic/cf-ngtcp2-proxy.c b/lib/vquic/cf-ngtcp2-proxy.c index f449484292..2f4792e8a9 100644 --- a/lib/vquic/cf-ngtcp2-proxy.c +++ b/lib/vquic/cf-ngtcp2-proxy.c @@ -27,46 +27,18 @@ defined(USE_PROXY_HTTP3) && defined(USE_NGHTTP3) && \ defined(USE_NGTCP2) && defined(USE_OPENSSL) -#include -#include - -#ifdef USE_OPENSSL -#include -#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL) -#include -#elif defined(OPENSSL_QUIC_API2) -#include -#else -#include -#endif -#include "vtls/openssl.h" -#endif - -#include - #include "urldata.h" #include "url.h" -#include "uint-hash.h" #include "curl_trc.h" -#include "rand.h" -#include "hash.h" #include "sendf.h" #include "multiif.h" #include "cfilters.h" -#include "cf-capsule.h" -#include "cf-socket.h" #include "connect.h" #include "progress.h" -#include "curlx/fopen.h" #include "curlx/dynbuf.h" -#include "dynhds.h" #include "http_proxy.h" -#include "select.h" #include "vquic/vquic.h" -#include "vquic/vquic_int.h" -#include "vquic/vquic-tls.h" -#include "vtls/vtls.h" -#include "vtls/vtls_scache.h" +#include "vquic/cf-ngtcp2-cmn.h" #include "vquic/cf-ngtcp2-proxy.h" #include "capsule.h" @@ -74,25 +46,7 @@ * each active transfer. We use HTTP/3 flow control and only ACK * when we take things out of the buffer. * Chunk size is large enough to take a full DATA frame */ -#define PROXY_H3_STREAM_WINDOW_SIZE (128 * 1024) -#define PROXY_H3_STREAM_WINDOW_SIZE_MAX (10 * 1024 * 1024) -#define PROXY_H3_STREAM_CHUNK_SIZE (16 * 1024) - -/* The pool keeps spares around and half of a full stream window - * seems good. More does not seem to improve performance. - * The benefit of the pool is that stream buffers do not keep - * spares. Memory consumption goes down when streams run empty, - * have a large upload done, etc. */ -#define PROXY_H3_STREAM_POOL_SPARES \ - ((PROXY_H3_STREAM_WINDOW_SIZE / PROXY_H3_STREAM_CHUNK_SIZE) / 2) - -#define PROXY_H3_STREAM_RECV_CHUNKS \ - (PROXY_H3_STREAM_WINDOW_SIZE / PROXY_H3_STREAM_CHUNK_SIZE) -#define PROXY_H3_STREAM_SEND_CHUNKS \ - (PROXY_H3_STREAM_WINDOW_SIZE / PROXY_H3_STREAM_CHUNK_SIZE) - -#define PROXY_QUIC_MAX_STREAMS (256 * 1024) -#define PROXY_QUIC_HANDSHAKE_TIMEOUT (10 * NGTCP2_SECONDS) +#define PROXY_H3_STREAM_RECV_CHUNKS ((512 * 1024) / H3_STREAM_CHUNK_SIZE) typedef enum { H3_TUNNEL_INIT, /* init/default/no tunnel state */ @@ -102,30 +56,32 @@ typedef enum { H3_TUNNEL_FAILED } h3_tunnel_state; -struct h3_proxy_stream_ctx; - struct h3_tunnel_stream { + struct Curl_peer *peer; /* where the tunnel goes to */ struct http_resp *resp; + struct bufq recvbuf; char *authority; - struct h3_proxy_stream_ctx *stream; - int64_t stream_id; + struct h3_stream_ctx *stream; h3_tunnel_state state; + BIT(udp); BIT(has_final_response); BIT(closed); }; static CURLcode h3_tunnel_stream_init(struct h3_tunnel_stream *ts, - struct Curl_peer *dest) + struct Curl_peer *peer, + bool udp) { ts->state = H3_TUNNEL_INIT; - ts->stream_id = -1; - ts->has_final_response = FALSE; - + Curl_peer_link(&ts->peer, peer); + Curl_bufq_init2(&ts->recvbuf, H3_STREAM_CHUNK_SIZE, + PROXY_H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + ts->udp = udp; /* host:port with IPv6 support */ - ts->authority = curl_maprintf("%s%s%s:%u", dest->ipv6 ? "[" : "", - dest->hostname, - dest->ipv6 ? "]" : "", - dest->port); + ts->authority = curl_maprintf("%s%s%s:%u", peer->ipv6 ? "[" : "", + peer->hostname, + peer->ipv6 ? "]" : "", + peer->port); if(!ts->authority) return CURLE_OUT_OF_MEMORY; @@ -134,34 +90,35 @@ static CURLcode h3_tunnel_stream_init(struct h3_tunnel_stream *ts, static void h3_tunnel_stream_reset(struct h3_tunnel_stream *ts) { + Curl_bufq_reset(&ts->recvbuf); Curl_http_resp_free(ts->resp); ts->resp = NULL; ts->stream = NULL; - ts->stream_id = -1; ts->has_final_response = FALSE; ts->closed = FALSE; ts->state = H3_TUNNEL_INIT; } -static void h3_tunnel_stream_clear(struct h3_tunnel_stream *ts) +static void h3_tunnel_stream_cleanup(struct h3_tunnel_stream *ts) { + Curl_peer_unlink(&ts->peer); + Curl_bufq_free(&ts->recvbuf); Curl_http_resp_free(ts->resp); curlx_safefree(ts->authority); - memset(ts, 0, sizeof(*ts)); ts->state = H3_TUNNEL_INIT; } static void h3_tunnel_go_state(struct Curl_cfilter *cf, struct h3_tunnel_stream *ts, h3_tunnel_state new_state, - struct Curl_easy *data, - bool udp_tunnel) + struct Curl_easy *data) { + VERBOSE(int64_t stream_id = ts->stream ? ts->stream->id : -1); (void)cf; - (void)udp_tunnel; if(ts->state == new_state) return; + /* leaving this one */ switch(ts->state) { case H3_TUNNEL_CONNECT: @@ -170,38 +127,32 @@ static void h3_tunnel_go_state(struct Curl_cfilter *cf, default: break; } + /* entering this one */ switch(new_state) { case H3_TUNNEL_INIT: - CURL_TRC_CF(data, cf, "[%" PRId64 "] new tunnel state 'init'", - ts->stream_id); + CURL_TRC_CF(data, cf, "[%" PRId64 "] -> [init]", stream_id); h3_tunnel_stream_reset(ts); break; - case H3_TUNNEL_CONNECT: - CURL_TRC_CF(data, cf, "[%" PRId64 "] new tunnel state 'connect'", - ts->stream_id); + CURL_TRC_CF(data, cf, "[%" PRId64 "] -> [connect]", stream_id); ts->state = H3_TUNNEL_CONNECT; break; - case H3_TUNNEL_RESPONSE: - CURL_TRC_CF(data, cf, "[%" PRId64 "] new tunnel state 'response'", - ts->stream_id); + CURL_TRC_CF(data, cf, "[%" PRId64 "] -> [response]", stream_id); ts->state = H3_TUNNEL_RESPONSE; break; - case H3_TUNNEL_ESTABLISHED: - CURL_TRC_CF(data, cf, "[%" PRId64 "] new tunnel state 'established'", - ts->stream_id); + CURL_TRC_CF(data, cf, "[%" PRId64 "] -> [established]", stream_id); infof(data, "CONNECT%s phase completed for HTTP/3 proxy", - udp_tunnel ? "-UDP" : ""); + ts->udp ? "-UDP" : ""); data->state.authproxy.done = TRUE; data->state.authproxy.multipass = FALSE; - FALLTHROUGH(); + ts->state = new_state; + curlx_safefree(data->req.hd_proxy_auth); + break; case H3_TUNNEL_FAILED: - if(new_state == H3_TUNNEL_FAILED) - CURL_TRC_CF(data, cf, "[%" PRId64 "] new tunnel state 'failed'", - ts->stream_id); + CURL_TRC_CF(data, cf, "[%" PRId64 "] -> [failed]", stream_id); ts->state = new_state; /* If a proxy-authorization header was used for the proxy, then we should make sure that it is not accidentally used for the document request @@ -211,437 +162,55 @@ static void h3_tunnel_go_state(struct Curl_cfilter *cf, } } -struct cf_ngtcp2_proxy_ctx { - struct cf_quic_ctx q; - struct ssl_peer peer; - struct curl_tls_ctx tls; -#ifdef OPENSSL_QUIC_API2 - ngtcp2_crypto_ossl_ctx *ossl_ctx; -#endif - ngtcp2_path connected_path; - ngtcp2_conn *qconn; - ngtcp2_cid dcid; - ngtcp2_cid scid; - uint32_t version; - ngtcp2_settings settings; - ngtcp2_transport_params transport_params; - ngtcp2_ccerr last_error; - ngtcp2_crypto_conn_ref conn_ref; - struct cf_call_data call_data; - nghttp3_conn *h3conn; - nghttp3_settings h3settings; - struct curltime started_at; /* time the current attempt started */ - struct curltime handshake_at; /* time connect handshake finished */ - struct bufc_pool stream_bufcp; /* chunk pool for streams */ - struct dynbuf scratch; /* temp buffer for header construction */ - struct uint_hash streams; /* hash data->mid to h3_proxy_stream_ctx */ - uint64_t used_bidi_streams; /* bidi streams we have opened */ - uint64_t max_bidi_streams; /* max bidi streams we can open */ - size_t earlydata_max; /* max amount of early data supported by - server on session reuse */ - size_t earlydata_skip; /* sending bytes to skip when earlydata - is accepted by peer */ - CURLcode tls_vrfy_result; /* result of TLS peer verification */ - int qlogfd; - BIT(initialized); - BIT(tls_handshake_complete); /* TLS handshake is done */ - BIT(use_earlydata); /* Using 0RTT data */ - BIT(earlydata_accepted); /* 0RTT was accepted by server */ - BIT(shutdown_started); /* queued shutdown packets */ -}; - struct cf_h3_proxy_ctx { - struct cf_ngtcp2_proxy_ctx *ngtcp2_ctx; - struct cf_call_data call_data; /* fallback before backend ctx exists */ - struct bufq inbufq; /* network receive buffer */ - struct Curl_peer *dest; /* where to tunnel to */ + struct cf_ngtcp2_ctx ngtcp2_ctx; struct h3_tunnel_stream tunnel; /* our tunnel CONNECT stream */ BIT(connected); - BIT(udp_tunnel); }; -/** - * All about the H3 internals of a stream - */ -struct h3_proxy_stream_ctx { - int64_t id; /* HTTP/3 stream identifier */ - struct bufq sendbuf; /* h3 request body */ - size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ - uint64_t error3; /* HTTP/3 stream error code */ - curl_off_t upload_left; /* number of request bytes left to upload */ - curl_off_t tun_data_recvd; /* number of bytes received over tunnel */ - uint64_t rx_offset; /* current receive offset */ - uint64_t rx_offset_max; /* allowed receive offset */ - uint64_t window_size_max; /* max flow control window set for stream */ - int status_code; /* HTTP status code */ - CURLcode xfer_result; /* result from xfer_resp_write(_hd) */ - BIT(resp_hds_complete); /* we have a complete, final response */ - BIT(closed); /* TRUE on stream close */ - BIT(reset); /* TRUE on stream reset */ - BIT(send_closed); /* stream is local closed */ - BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ -}; +static CURLcode cf_ngtcp2_proxy_h3_init(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_ctx *ctx); -#define H3_PROXY_STREAM_CTX(ctx, data) \ - ((data) ? Curl_uint32_hash_get(&(ctx)->streams, (data)->mid) : NULL) - -#define H3_STREAM_ID(stream) ((stream)->id) - -static void h3_proxy_stream_ctx_free(struct h3_proxy_stream_ctx *stream) +static CURLcode cf_h3_proxy_ctx_init(struct cf_h3_proxy_ctx *ctx, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { - Curl_bufq_free(&stream->sendbuf); - curlx_free(stream); -} - -static void h3_proxy_stream_hash_free(unsigned int id, void *stream) -{ - (void)id; - DEBUGASSERT(stream); - h3_proxy_stream_ctx_free((struct h3_proxy_stream_ctx *)stream); -} - -static void cf_ngtcp2_proxy_ctx_init(struct cf_ngtcp2_proxy_ctx *ctx) -{ - DEBUGASSERT(!ctx->initialized); - ctx->q.sockfd = CURL_SOCKET_BAD; - ctx->qlogfd = -1; - ctx->version = NGTCP2_PROTO_VER_MAX; - Curl_bufcp_init(&ctx->stream_bufcp, PROXY_H3_STREAM_CHUNK_SIZE, - PROXY_H3_STREAM_POOL_SPARES); - curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); - Curl_uint32_hash_init(&ctx->streams, 63, h3_proxy_stream_hash_free); - ctx->initialized = TRUE; -} - -static void cf_ngtcp2_proxy_ctx_free(struct cf_ngtcp2_proxy_ctx *ctx) -{ - if(ctx && ctx->initialized) { - Curl_vquic_tls_cleanup(&ctx->tls); - vquic_ctx_free(&ctx->q); - Curl_bufcp_free(&ctx->stream_bufcp); - curlx_dyn_free(&ctx->scratch); - Curl_uint32_hash_destroy(&ctx->streams); - Curl_ssl_peer_cleanup(&ctx->peer); - } - curlx_free(ctx); -} - -static void cf_ngtcp2_proxy_ctx_close(struct cf_ngtcp2_proxy_ctx *ctx) -{ - struct cf_call_data save = ctx->call_data; - - if(!ctx->initialized) - return; - if(ctx->qlogfd != -1) { - curlx_close(ctx->qlogfd); - } - ctx->qlogfd = -1; - Curl_vquic_tls_cleanup(&ctx->tls); - Curl_ssl_peer_cleanup(&ctx->peer); - vquic_ctx_free(&ctx->q); - if(ctx->h3conn) { - nghttp3_conn_del(ctx->h3conn); - ctx->h3conn = NULL; - } - if(ctx->qconn) { - ngtcp2_conn_del(ctx->qconn); - ctx->qconn = NULL; - } -#ifdef OPENSSL_QUIC_API2 - if(ctx->ossl_ctx) { - ngtcp2_crypto_ossl_ctx_del(ctx->ossl_ctx); - ctx->ossl_ctx = NULL; - } -#endif - ctx->call_data = save; -} - -static void cf_ngtcp2_proxy_setup_keep_alive(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - const ngtcp2_transport_params *rp; - /* Peer should have sent us its transport parameters. If it - * announces a positive `max_idle_timeout` it will close the - * connection when it does not hear from us for that time. - * - * Some servers use this as a keep-alive timer at a rather low - * value. We are doing HTTP/3 here and waiting for the response - * to a request may take a considerable amount of time. We need - * to prevent the peer's QUIC stack from closing in this case. - */ - if(!ctx->qconn) - return; - - rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); - if(!rp || !rp->max_idle_timeout) { - ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); - CURL_TRC_CF(data, cf, "no peer idle timeout, unset keep-alive"); - } - else if(!Curl_uint32_hash_count(&ctx->streams)) { - ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); - CURL_TRC_CF(data, cf, "no active streams, unset keep-alive"); - } - else { - ngtcp2_duration keep_ns; - keep_ns = (rp->max_idle_timeout > 1) ? (rp->max_idle_timeout / 2) : 1; - ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, keep_ns); - CURL_TRC_CF(data, cf, "peer idle timeout is %" PRIu64 "ms, " - "set keep-alive to %" PRIu64 " ms.", - rp->max_idle_timeout / NGTCP2_MILLISECONDS, - keep_ns / NGTCP2_MILLISECONDS); - } -} - -struct proxy_pkt_io_ctx { - struct Curl_cfilter *cf; - struct Curl_easy *data; - ngtcp2_tstamp ts; - ngtcp2_path_storage ps; -}; - -static void proxy_pktx_update_time(struct proxy_pkt_io_ctx *pktx, - struct Curl_cfilter *cf) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - const struct curltime *pnow = Curl_pgrs_now(pktx->data); - - vquic_ctx_update_time(&ctx->q, pnow); - pktx->ts = ((ngtcp2_tstamp)pnow->tv_sec * NGTCP2_SECONDS) + - ((ngtcp2_tstamp)pnow->tv_usec * NGTCP2_MICROSECONDS); -} - -static void proxy_pktx_init(struct proxy_pkt_io_ctx *pktx, - struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - const struct curltime *pnow = Curl_pgrs_now(data); - - pktx->cf = cf; - pktx->data = data; - ngtcp2_path_storage_zero(&pktx->ps); - vquic_ctx_set_time(&ctx->q, pnow); - pktx->ts = ((ngtcp2_tstamp)pnow->tv_sec * NGTCP2_SECONDS) + - ((ngtcp2_tstamp)pnow->tv_usec * NGTCP2_MICROSECONDS); -} - -static ngtcp2_conn *proxy_get_conn(ngtcp2_crypto_conn_ref *conn_ref) -{ - struct Curl_cfilter *cf = conn_ref->user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - return ctx->qconn; -} - -#ifdef DEBUG_NGTCP2 -static void proxy_quic_printf(void *user_data, const char *fmt, ...) -{ - va_list ap; - (void)user_data; - va_start(ap, fmt); - curl_mvfprintf(stderr, fmt, ap); - va_end(ap); - curl_mfprintf(stderr, "\n"); -} -#endif - -static void proxy_qlog_callback(void *user_data, uint32_t flags, - const void *data, size_t datalen) -{ - struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - (void)flags; - if(ctx->qlogfd != -1) { - ssize_t rc = write(ctx->qlogfd, data, datalen); - if(rc == -1) { - /* on write error, stop further write attempts */ - curlx_close(ctx->qlogfd); - ctx->qlogfd = -1; - } - } -} - -static void quic_settings_proxy(struct cf_ngtcp2_proxy_ctx *ctx, - struct Curl_easy *data, - struct proxy_pkt_io_ctx *pktx) -{ - ngtcp2_settings *s = &ctx->settings; - ngtcp2_transport_params *t = &ctx->transport_params; - - ngtcp2_settings_default(s); - ngtcp2_transport_params_default(t); -#ifdef DEBUG_NGTCP2 - s->log_printf = proxy_quic_printf; -#else - s->log_printf = NULL; -#endif - - s->initial_ts = pktx->ts; - s->handshake_timeout = (data->set.connecttimeout > 0) ? - data->set.connecttimeout * NGTCP2_MILLISECONDS : - PROXY_QUIC_HANDSHAKE_TIMEOUT; - s->max_window = 100 * PROXY_H3_STREAM_WINDOW_SIZE; - s->max_stream_window = 10 * PROXY_H3_STREAM_WINDOW_SIZE; - s->no_pmtud = FALSE; -#ifdef NGTCP2_SETTINGS_V3 - /* try ten times the ngtcp2 defaults here for problems with Caddy */ - s->glitch_ratelim_burst = 1000 * 10; - s->glitch_ratelim_rate = 33 * 10; -#endif - t->initial_max_data = 10 * PROXY_H3_STREAM_WINDOW_SIZE; - t->initial_max_stream_data_bidi_local = PROXY_H3_STREAM_WINDOW_SIZE; - t->initial_max_stream_data_bidi_remote = PROXY_H3_STREAM_WINDOW_SIZE; - t->initial_max_stream_data_uni = PROXY_H3_STREAM_WINDOW_SIZE; - t->initial_max_streams_bidi = PROXY_QUIC_MAX_STREAMS; - t->initial_max_streams_uni = PROXY_QUIC_MAX_STREAMS; - t->max_idle_timeout = 0; /* no idle timeout from our side */ - if(ctx->qlogfd != -1) { - s->qlog_write = proxy_qlog_callback; - } -} - -static void cf_ngtcp2_proxy_conn_close(struct Curl_cfilter *cf, - struct Curl_easy *data); - -static bool cf_ngtcp2_proxy_err_is_fatal(int code) -{ - return (NGTCP2_ERR_FATAL >= code) || - (NGTCP2_ERR_DROP_CONN == code) || - (NGTCP2_ERR_IDLE_CLOSE == code); -} - -static void cf_ngtcp2_proxy_err_set(struct Curl_cfilter *cf, - struct Curl_easy *data, int code) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - if(!ctx->last_error.error_code) { - if(NGTCP2_ERR_CRYPTO == code) { - ngtcp2_ccerr_set_tls_alert(&ctx->last_error, - ngtcp2_conn_get_tls_alert(ctx->qconn), - NULL, 0); - } - else { - ngtcp2_ccerr_set_liberr(&ctx->last_error, code, NULL, 0); - } - } - if(cf_ngtcp2_proxy_err_is_fatal(code)) - cf_ngtcp2_proxy_conn_close(cf, data); -} - -static bool cf_ngtcp2_proxy_h3_err_is_fatal(int code) -{ - return (NGHTTP3_ERR_FATAL >= code) || - (NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM == code); -} - -static void cf_ngtcp2_proxy_h3_err_set(struct Curl_cfilter *cf, - struct Curl_easy *data, int code) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - if(!ctx->last_error.error_code) { - ngtcp2_ccerr_set_application_error(&ctx->last_error, - nghttp3_err_infer_quic_app_error_code(code), NULL, 0); - } - if(cf_ngtcp2_proxy_h3_err_is_fatal(code)) - cf_ngtcp2_proxy_conn_close(cf, data); -} - -/* How to access `call_data` from a cf_h3_proxy filter */ -static struct cf_call_data *cf_h3_proxy_call_data(struct Curl_cfilter *cf) -{ - struct cf_h3_proxy_ctx *ctx = cf ? cf->ctx : NULL; - static struct cf_call_data no_ctx; - - if(!ctx) - return &no_ctx; - if(ctx->ngtcp2_ctx) - return &ctx->ngtcp2_ctx->call_data; - return &ctx->call_data; -} - -#undef CF_CTX_CALL_DATA -#define CF_CTX_CALL_DATA(cf) (*cf_h3_proxy_call_data(cf)) - -static void cf_h3_proxy_ctx_clear(struct cf_h3_proxy_ctx *ctx) -{ - Curl_bufq_free(&ctx->inbufq); - Curl_peer_unlink(&ctx->dest); - h3_tunnel_stream_clear(&ctx->tunnel); - memset(ctx, 0, sizeof(*ctx)); + CURLcode result; + result = Curl_cf_ngtcp2_ctx_init(&ctx->ngtcp2_ctx, origin, peer, + sslc, cf_ngtcp2_proxy_h3_init); + if(!result) + result = h3_tunnel_stream_init(&ctx->tunnel, tunnel_peer, + TRNSPRT_IS_DGRAM(tunnel_transport)); + return result; } static void cf_h3_proxy_ctx_free(struct cf_h3_proxy_ctx *ctx) { if(ctx) { - cf_h3_proxy_ctx_clear(ctx); + Curl_cf_ngtcp2_ctx_cleanup(&ctx->ngtcp2_ctx); + h3_tunnel_stream_cleanup(&ctx->tunnel); curlx_free(ctx); } } -static CURLcode h3_proxy_data_setup(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct h3_proxy_stream_ctx *stream = NULL; - - if(!data) - return CURLE_FAILED_INIT; - - if(!ctx) - return CURLE_FAILED_INIT; - - stream = H3_PROXY_STREAM_CTX(ctx, data); - if(stream) - return CURLE_OK; - - stream = curlx_calloc(1, sizeof(*stream)); - if(!stream) - return CURLE_OUT_OF_MEMORY; - - stream->id = -1; - stream->rx_offset = 0; - stream->rx_offset_max = PROXY_H3_STREAM_WINDOW_SIZE; - /* on send, we control how much we put into the buffer */ - Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, - PROXY_H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); - stream->sendbuf_len_in_flight = 0; - stream->window_size_max = PROXY_H3_STREAM_WINDOW_SIZE; - - if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { - h3_proxy_stream_ctx_free(stream); - return CURLE_OUT_OF_MEMORY; - } - - if(Curl_uint32_hash_count(&ctx->streams) == 1) - cf_ngtcp2_proxy_setup_keep_alive(cf, data); - - return CURLE_OK; -} - static int cb_h3_proxy_acked_req_body(nghttp3_conn *conn, int64_t stream_id, uint64_t datalen, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *data = stream_user_data; - struct h3_proxy_stream_ctx *stream; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct h3_stream_ctx *stream; size_t skiplen; + (void)stream_user_data; - if(!ctx) - return 0; - stream = H3_PROXY_STREAM_CTX(ctx, data); - if(!stream) + stream = pctx->tunnel.stream; + if(!stream || (stream->id != stream_id)) return 0; + /* The server acknowledged `datalen` of bytes from our request body. * This is a delta. We have kept this data in `sendbuf` for * re-transmissions and can free it now. */ @@ -667,25 +236,18 @@ static int cb_h3_proxy_stream_close(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *data = stream_user_data; - struct h3_proxy_stream_ctx *stream; - bool tunnel_stream = FALSE; - (void)conn; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct h3_stream_ctx *stream; - if(!ctx) + (void)conn; + (void)stream_user_data; + if(!data) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + stream = pctx->tunnel.stream; + if(!stream || (stream->id != stream_id)) return 0; - stream = H3_PROXY_STREAM_CTX(ctx, data); - tunnel_stream = (stream_id == proxy_ctx->tunnel.stream_id); - /* we might be called by nghttp3 after we already cleaned up */ - if(!stream) { - if(tunnel_stream) { - proxy_ctx->tunnel.stream = NULL; - proxy_ctx->tunnel.closed = TRUE; - } - return 0; - } stream->closed = TRUE; stream->error3 = app_error_code; @@ -693,26 +255,23 @@ static int cb_h3_proxy_stream_close(nghttp3_conn *conn, int64_t stream_id, stream->reset = TRUE; stream->send_closed = TRUE; CURL_TRC_CF(data, cf, "[%" PRId64 "] RESET: error %" PRIu64, - H3_STREAM_ID(stream), stream->error3); - } - else { - CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", H3_STREAM_ID(stream)); - } - if(tunnel_stream) { - proxy_ctx->tunnel.stream = NULL; - proxy_ctx->tunnel.closed = TRUE; + stream->id, stream->error3); } + else + CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", stream->id); + pctx->tunnel.stream = NULL; + pctx->tunnel.closed = TRUE; Curl_multi_mark_dirty(data); return 0; } static void cf_h3_proxy_upd_rx_win(struct Curl_cfilter *cf, struct Curl_easy *data, - struct h3_proxy_stream_ctx *stream) + struct h3_stream_ctx *stream) { - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - uint64_t cur_win, wanted_win = PROXY_H3_STREAM_WINDOW_SIZE_MAX; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + uint64_t cur_win, wanted_win = H3_STREAM_WINDOW_SIZE_MAX; /* how much does rate limiting allow us to acknowledge? */ if(Curl_rlimit_active(&data->progress.dl.rlimit)) { @@ -729,7 +288,7 @@ static void cf_h3_proxy_upd_rx_win(struct Curl_cfilter *cf, " tokens)", stream->id, avail); return; } - wanted_win = CURLMIN((uint64_t)avail, PROXY_H3_STREAM_WINDOW_SIZE_MAX); + wanted_win = CURLMIN((uint64_t)avail, H3_STREAM_WINDOW_SIZE_MAX); } if(stream->rx_offset_max < stream->rx_offset) { @@ -761,28 +320,26 @@ static int cb_h3_proxy_recv_data(nghttp3_conn *conn, int64_t stream3_id, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *data = stream_user_data; - struct h3_proxy_stream_ctx *stream; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct h3_stream_ctx *stream; size_t nwritten; CURLcode result = CURLE_OK; (void)conn; (void)stream3_id; + (void)stream_user_data; - if(!ctx) - return NGHTTP3_ERR_CALLBACK_FAILURE; - stream = H3_PROXY_STREAM_CTX(ctx, data); - if(!stream) { + stream = pctx->tunnel.stream; + if(!data || !stream || (stream->id != stream3_id)) { return NGHTTP3_ERR_CALLBACK_FAILURE; } - stream->tun_data_recvd += (curl_off_t)buflen; + stream->rx_total += (curl_off_t)buflen; CURL_TRC_CF(data, cf, "[cb_h3_proxy_recv_data] " "[%" PRId64 "] DATA len=%zu, total=%" FMT_OFF_T, - H3_STREAM_ID(stream), buflen, stream->tun_data_recvd); + stream->id, buflen, stream->rx_total); - result = Curl_bufq_write(&proxy_ctx->inbufq, buf, buflen, &nwritten); + result = Curl_bufq_write(&pctx->tunnel.recvbuf, buf, buflen, &nwritten); if(result || (nwritten < buflen)) { return NGHTTP3_ERR_CALLBACK_FAILURE; } @@ -807,8 +364,8 @@ static int cb_h3_proxy_deferred_consume(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; (void)conn; (void)stream_user_data; @@ -829,12 +386,11 @@ static int cb_h3_proxy_recv_header(nghttp3_conn *conn, int64_t stream_id, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; + struct cf_h3_proxy_ctx *pctx = cf->ctx; nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); - struct Curl_easy *data = stream_user_data; - struct h3_proxy_stream_ctx *stream; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct h3_stream_ctx *stream; CURLcode result = CURLE_OK; int http_status; struct http_resp *resp; @@ -842,27 +398,21 @@ static int cb_h3_proxy_recv_header(nghttp3_conn *conn, int64_t stream_id, (void)stream_id; (void)token; (void)flags; + (void)stream_user_data; /* stream_user_data might be NULL for control streams */ - if(!data) { - /* Silently ignore headers on streams without user data (control, etc) */ - return 0; - } + if(!data) + return NGHTTP3_ERR_CALLBACK_FAILURE; - if(!ctx) - return 0; - stream = H3_PROXY_STREAM_CTX(ctx, data); - if(!stream) { + stream = pctx->tunnel.stream; + if(!stream || (stream->id != stream_id)) { CURL_TRC_CF(data, cf, "[%" PRId64 "] recv_header: stream lookup " "failed for data=%p mid=%u", stream_id, (void *)data, data ? data->mid : 0); + return 0; } - /* we might have cleaned up this transfer already */ - if(!stream) - return 0; - - if(proxy_ctx->tunnel.has_final_response) { + if(pctx->tunnel.has_final_response) { /* we do not do anything with trailers for tunnel streams */ return 0; } @@ -876,15 +426,15 @@ static int cb_h3_proxy_recv_header(nghttp3_conn *conn, int64_t stream_id, result = Curl_http_resp_make(&resp, http_status, NULL); if(result) return NGHTTP3_ERR_CALLBACK_FAILURE; - if(proxy_ctx->tunnel.resp) - Curl_http_resp_free(proxy_ctx->tunnel.resp); - proxy_ctx->tunnel.resp = resp; + if(pctx->tunnel.resp) + Curl_http_resp_free(pctx->tunnel.resp); + pctx->tunnel.resp = resp; } else { /* store as an HTTP1-style header */ CURL_TRC_CF(data, cf, "[%" PRId64 "] header: %.*s: %.*s", stream_id, (int)h3name.len, h3name.base, (int)h3val.len, h3val.base); - result = Curl_dynhds_add(&proxy_ctx->tunnel.resp->headers, + result = Curl_dynhds_add(&pctx->tunnel.resp->headers, (const char *)h3name.base, h3name.len, (const char *)h3val.base, h3val.len); if(result) { @@ -899,38 +449,31 @@ static int cb_h3_proxy_end_headers(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *data = stream_user_data; - struct h3_proxy_stream_ctx *stream; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct h3_stream_ctx *stream; (void)conn; (void)stream_id; (void)fin; + (void)stream_user_data; - /* stream_user_data might be NULL for control streams */ - if(!data) { - /* Silently ignore for streams without user data */ - return 0; - } + if(!data) + return NGHTTP3_ERR_CALLBACK_FAILURE; - if(!ctx) - return 0; - stream = H3_PROXY_STREAM_CTX(ctx, data); - if(!stream) { + stream = pctx->tunnel.stream; + if(!stream || (stream->id != stream_id)) { CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers: stream lookup " "failed for data=%p mid=%u", stream_id, (void *)data, data ? data->mid : 0); - } - - if(!stream) return 0; + } CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers, status=%d", stream_id, stream->status_code); - if(!proxy_ctx->tunnel.has_final_response) { + if(!pctx->tunnel.has_final_response) { if(stream->status_code / 100 != 1) { - proxy_ctx->tunnel.has_final_response = TRUE; + pctx->tunnel.has_final_response = TRUE; } } @@ -947,10 +490,9 @@ static int cb_h3_proxy_stop_sending(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; (void)conn; - (void)stream_user_data; if(ctx) { @@ -970,55 +512,57 @@ static int cb_h3_proxy_reset_stream(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *data = stream_user_data; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); int rv; - (void)conn; - if(!ctx) + (void)conn; + (void)stream_user_data; + if(!data) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + if(!pctx->tunnel.stream || + (stream_id != pctx->tunnel.stream->id)) return 0; rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, 0, stream_id, app_error_code); CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); - if(stream_id == proxy_ctx->tunnel.stream_id) { - proxy_ctx->tunnel.stream = NULL; - proxy_ctx->tunnel.closed = TRUE; - } + pctx->tunnel.stream = NULL; + pctx->tunnel.closed = TRUE; if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { return NGHTTP3_ERR_CALLBACK_FAILURE; } - return 0; } -static nghttp3_ssize cb_h3_read_data_for_tunnel_stream(nghttp3_conn *conn, - int64_t stream_id, - nghttp3_vec *vec, - size_t veccnt, - uint32_t *pflags, - void *user_data, - void *stream_user_data) +static nghttp3_ssize cb_h3_tunnel_read_data(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_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *data = stream_user_data; - struct h3_proxy_stream_ctx *stream; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct h3_stream_ctx *stream; size_t nwritten = 0; size_t nvecs = 0; const unsigned char *buf_base; + (void)conn; (void)stream_id; (void)veccnt; + (void)stream_user_data; + (void)pflags; - if(!ctx) + stream = pctx->tunnel.stream; + if(!data || !stream || (stream->id != stream_id)) return NGHTTP3_ERR_CALLBACK_FAILURE; - stream = H3_PROXY_STREAM_CTX(ctx, data); - if(!stream) - return NGHTTP3_ERR_CALLBACK_FAILURE; /* nghttp3 keeps references to the sendbuf data until it is ACKed * by the server (see `cb_h3_proxy_acked_req_body()` for updates). * `sendbuf_len_in_flight` is the amount of bytes in `sendbuf` @@ -1042,33 +586,18 @@ static nghttp3_ssize cb_h3_read_data_for_tunnel_stream(nghttp3_conn *conn, DEBUGASSERT(nvecs > 0); /* we SHOULD have been be able to peek */ } - if(nwritten > 0 && - stream->upload_left != -1 && - (H3_STREAM_ID(stream) != proxy_ctx->tunnel.stream_id)) - stream->upload_left -= nwritten; - - /* When we stopped sending and everything in `sendbuf` is "in flight", - * we are at the end of the request body. */ - /* We should NOT set send_closed = TRUE for tunnel stream */ - if(stream->upload_left == 0 && - (H3_STREAM_ID(stream) != proxy_ctx->tunnel.stream_id)) { - *pflags = NGHTTP3_DATA_FLAG_EOF; - stream->send_closed = TRUE; - } - - else if(!nwritten) { + if(!nwritten) { /* Not EOF, and nothing to give, we signal WOULDBLOCK. */ CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> AGAIN", - H3_STREAM_ID(stream)); + stream->id); return NGHTTP3_ERR_WOULDBLOCK; } CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> " - "%zu vecs%s with %zu (buffered=%zu, left=%" FMT_OFF_T ")", - H3_STREAM_ID(stream), nvecs, + "%zu vecs%s with %zu (buffered=%zu)", + stream->id, nvecs, *pflags == NGHTTP3_DATA_FLAG_EOF ? " EOF" : "", - nwritten, Curl_bufq_len(&stream->sendbuf), - stream->upload_left); + nwritten, Curl_bufq_len(&stream->sendbuf)); return (nghttp3_ssize)nvecs; } @@ -1098,55 +627,10 @@ static nghttp3_callbacks ngh3_proxy_callbacks = { #endif }; -#if NGTCP2_VERSION_NUM < 0x011100 -struct cf_ngtcp2_proxy_sfind_ctx { - int64_t stream_id; - struct h3_proxy_stream_ctx *stream; - uint32_t mid; -}; - -static bool cf_ngtcp2_proxy_sfind(uint32_t mid, void *value, void *user_data) +static CURLcode cf_ngtcp2_proxy_h3_init(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_ngtcp2_ctx *ctx) { - struct cf_ngtcp2_proxy_sfind_ctx *fctx = user_data; - struct h3_proxy_stream_ctx *stream = value; - - if(fctx->stream_id == H3_STREAM_ID(stream)) { - fctx->mid = mid; - fctx->stream = stream; - return FALSE; - } - return TRUE; /* continue */ -} - -static struct h3_proxy_stream_ctx *cf_ngtcp2_proxy_get_stream( - struct cf_ngtcp2_proxy_ctx *ctx, int64_t stream_id) -{ - struct cf_ngtcp2_proxy_sfind_ctx fctx; - fctx.stream_id = stream_id; - fctx.stream = NULL; - Curl_uint32_hash_visit(&ctx->streams, cf_ngtcp2_proxy_sfind, &fctx); - return fctx.stream; -} -#else -static struct h3_proxy_stream_ctx *cf_ngtcp2_proxy_get_stream( - struct cf_ngtcp2_proxy_ctx *ctx, int64_t stream_id) -{ - struct Curl_easy *data = - ngtcp2_conn_get_stream_user_data(ctx->qconn, stream_id); - - if(!data) { - return NULL; - } - return H3_PROXY_STREAM_CTX(ctx, data); -} -#endif /* NGTCP2_VERSION_NUM < 0x011100 */ - -static CURLcode cf_ngtcp2_h3conn_init(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id; int rc; if(ngtcp2_conn_get_streams_uni_left(ctx->qconn) < 3) { @@ -1166,963 +650,13 @@ static CURLcode cf_ngtcp2_h3conn_init(struct Curl_cfilter *cf, return CURLE_OUT_OF_MEMORY; } - rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL); - if(rc) { - failf(data, "error creating HTTP/3 control stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = nghttp3_conn_bind_control_stream(ctx->h3conn, ctrl_stream_id); - if(rc) { - failf(data, "error binding HTTP/3 control stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_enc_stream_id, NULL); - if(rc) { - failf(data, "error creating HTTP/3 qpack encoding stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_dec_stream_id, NULL); - if(rc) { - failf(data, "error creating HTTP/3 qpack decoding stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = nghttp3_conn_bind_qpack_streams(ctx->h3conn, qpack_enc_stream_id, - qpack_dec_stream_id); - if(rc) { - failf(data, "error binding HTTP/3 qpack streams: %s", ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - CURL_TRC_CF(data, cf, "HTTP/3 connection initialized"); - return CURLE_OK; + return Curl_cf_ngtcp2_h3_init_ctrls(ctx, data); } -static int cb_ngtcp2_proxy_handshake_completed(ngtcp2_conn *tconn, - void *user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *data; - - (void)tconn; - DEBUGASSERT(ctx); - data = CF_DATA_CURRENT(cf); - DEBUGASSERT(data); - if(!ctx || !data) - return NGTCP2_ERR_CALLBACK_FAILURE; - - ctx->handshake_at = *Curl_pgrs_now(data); - ctx->tls_handshake_complete = TRUE; - Curl_vquic_report_handshake(&ctx->tls, cf, data); - - ctx->tls_vrfy_result = Curl_vquic_tls_verify_peer(&ctx->tls, cf, - data, &ctx->peer); -#ifdef CURLVERBOSE - if(Curl_trc_is_verbose(data)) { - const ngtcp2_transport_params *rp; - rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); - CURL_TRC_CF(data, cf, "handshake complete after %" FMT_TIMEDIFF_T - "ms, remote transport[max_udp_payload=%" PRIu64 - ", initial_max_data=%" PRIu64 "]", - curlx_ptimediff_ms(&ctx->handshake_at, &ctx->started_at), - rp->max_udp_payload_size, rp->initial_max_data); - } -#endif - - /* In case of earlydata, where we simulate being connected, update - * the handshake time when we really did connect */ - if(ctx->use_earlydata) - Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); - if(ctx->use_earlydata) { -#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) - ctx->earlydata_accepted = - (SSL_get_early_data_status(ctx->tls.ossl.ssl) != - SSL_EARLY_DATA_REJECTED); -#endif -#ifdef USE_GNUTLS - int flags = gnutls_session_get_flags(ctx->tls.gtls.session); - ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA); -#endif -#ifdef USE_WOLFSSL -#ifdef WOLFSSL_EARLY_DATA - ctx->earlydata_accepted = - (wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) != - WOLFSSL_EARLY_DATA_REJECTED); -#else - DEBUGASSERT(0); /* should not come here if ED is disabled. */ - ctx->earlydata_accepted = FALSE; -#endif /* WOLFSSL_EARLY_DATA */ -#endif - CURL_TRC_CF(data, cf, "server did%s accept %zu bytes of early data", - ctx->earlydata_accepted ? "" : " not", ctx->earlydata_skip); - Curl_pgrsEarlyData(data, ctx->earlydata_accepted ? - (curl_off_t)ctx->earlydata_skip : - -(curl_off_t)ctx->earlydata_skip); - } - - /* Initialize HTTP/3 connection after successful handshake */ - if(!ctx->h3conn) { - CURLcode result = cf_ngtcp2_h3conn_init(cf, data); - if(result) { - CURL_TRC_CF(data, cf, "HTTP/3 initialization failed: %d", result); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - } - - return 0; -} - -static int cb_ngtcp2_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 Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - nghttp3_ssize nconsumed; - int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; - struct Curl_easy *data = stream_user_data; - (void)offset; - (void)data; - - nconsumed = - nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin); - if(!data) - data = CF_DATA_CURRENT(cf); - if(data) - CURL_TRC_CF(data, cf, "[%" PRId64 "] read_stream(len=%zu) -> %zd", - stream_id, buflen, nconsumed); - if(nconsumed < 0) { - struct h3_proxy_stream_ctx *stream = H3_PROXY_STREAM_CTX(ctx, data); - if(data && stream) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] error on known stream, " - "reset=%d, closed=%d", - stream_id, stream->reset, stream->closed); - } - 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. */ - if(nconsumed) { - ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, nconsumed); - ngtcp2_conn_extend_max_offset(tconn, nconsumed); - } - - return 0; -} - -static int cb_ngtcp2_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 Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - int rv; - (void)stream_id; - (void)tconn; - (void)offset; - (void)datalen; - (void)stream_user_data; - - rv = nghttp3_conn_add_ack_offset(ctx->h3conn, stream_id, datalen); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} - -static int cb_ngtcp2_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 Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *data = stream_user_data; - int rv; - - (void)tconn; - /* stream is closed... */ - if(!data) - data = CF_DATA_CURRENT(cf); - if(!data) - return NGTCP2_ERR_CALLBACK_FAILURE; - - if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { - app_error_code = NGHTTP3_H3_NO_ERROR; - } - - rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id, app_error_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] quic close(app_error=%" - PRIu64 ") -> %d", stream_id, app_error_code, rv); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - cf_ngtcp2_proxy_h3_err_set(cf, data, rv); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} - -static int cb_ngtcp2_extend_max_local_streams_bidi(ngtcp2_conn *tconn, - uint64_t max_streams, - void *user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *data = CF_DATA_CURRENT(cf); - - (void)tconn; - ctx->max_bidi_streams = max_streams; - if(data) - CURL_TRC_CF(data, cf, "max bidi streams now %" PRIu64 ", used %" PRIu64, - ctx->max_bidi_streams, ctx->used_bidi_streams); - return 0; -} - -static void cb_ngtcp2_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, fill 0 and call it *random*. */ - memset(dest, 0, destlen); - } -} - -/* for ngtcp2 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; -} - -#ifdef NGTCP2_CALLBACKS_V3 /* ngtcp2 v1.22.0+ */ -static int cb_ngtcp2_get_new_connection_id2(ngtcp2_conn *tconn, - ngtcp2_cid *cid, struct ngtcp2_stateless_reset_token *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->data, sizeof(token->data)); - if(result) - return NGTCP2_ERR_CALLBACK_FAILURE; - - return 0; -} -#endif - -static int cb_ngtcp2_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 Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *data = stream_user_data; - int rv; - (void)tconn; - (void)final_size; - (void)app_error_code; - (void)data; - - rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); - if(stream_id == proxy_ctx->tunnel.stream_id) { - proxy_ctx->tunnel.stream = NULL; - proxy_ctx->tunnel.closed = TRUE; - } - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} - -static int cb_ngtcp2_extend_max_stream_data(ngtcp2_conn *tconn, - int64_t stream_id, - uint64_t max_data, void *user_data, - void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *s_data = stream_user_data; - struct h3_proxy_stream_ctx *stream = NULL; - int rv; - (void)tconn; - (void)max_data; - - rv = nghttp3_conn_unblock_stream(ctx->h3conn, stream_id); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - stream = H3_PROXY_STREAM_CTX(ctx, s_data); - if(stream && stream->quic_flow_blocked) { - CURL_TRC_CF(s_data, cf, "[%" PRId64 "] unblock quic flow", stream_id); - stream->quic_flow_blocked = FALSE; - Curl_multi_mark_dirty(s_data); - } - return 0; -} - -static int cb_ngtcp2_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id, - uint64_t app_error_code, - void *user_data, - void *stream_user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - int rv; - (void)tconn; - (void)app_error_code; - (void)stream_user_data; - - rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} - -static int cb_ngtcp2_recv_rx_key(ngtcp2_conn *tconn, - ngtcp2_encryption_level level, - void *user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct Curl_easy *data = CF_DATA_CURRENT(cf); - (void)tconn; - - if(level != NGTCP2_ENCRYPTION_LEVEL_1RTT) - return 0; - - DEBUGASSERT(ctx); - DEBUGASSERT(data); - if(ctx && data && !ctx->h3conn) { - if(cf_ngtcp2_h3conn_init(cf, data)) - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} - -#if defined(_MSC_VER) && defined(_DLL) -#pragma warning(push) -#pragma warning(disable:4232) /* MSVC extension, dllimport identity */ -#endif - -static ngtcp2_callbacks ngtcp2_proxy_callbacks = { - ngtcp2_crypto_client_initial_cb, - NULL, /* recv_client_initial */ - ngtcp2_crypto_recv_crypto_data_cb, - cb_ngtcp2_proxy_handshake_completed, - NULL, /* recv_version_negotiation */ - ngtcp2_crypto_encrypt_cb, - ngtcp2_crypto_decrypt_cb, - ngtcp2_crypto_hp_mask_cb, - cb_ngtcp2_recv_stream_data, - cb_ngtcp2_acked_stream_data_offset, - NULL, /* stream_open */ - cb_ngtcp2_stream_close, - NULL, /* recv_stateless_reset */ - ngtcp2_crypto_recv_retry_cb, - cb_ngtcp2_extend_max_local_streams_bidi, - NULL, /* extend_max_local_streams_uni */ - cb_ngtcp2_rand, - cb_ngtcp2_get_new_connection_id, /* for ngtcp2 cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - ngtcp2_pkt_info pi; - ngtcp2_path path; - size_t offset, pktlen; - int rv; - - if(ecn) - CURL_TRC_CF(pktx->data, pktx->cf, "vquic_recv(len=%zu, gso=%zu, ecn=%x)", - buflen, gso_size, ecn); - ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr, - ctx->q.local_addrlen); - ngtcp2_addr_init(&path.remote, (struct sockaddr *)remote_addr, - remote_addrlen); - pi.ecn = (uint8_t)ecn; - - for(offset = 0; offset < buflen; offset += gso_size) { - pktlen = ((offset + gso_size) <= buflen) ? gso_size : (buflen - offset); - rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, - buf + offset, pktlen, pktx->ts); - if(rv) { - CURL_TRC_CF(pktx->data, pktx->cf, "ingress, read_pkt -> %s (%d)", - ngtcp2_strerror(rv), rv); - cf_ngtcp2_proxy_err_set(pktx->cf, pktx->data, rv); - - 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 proxy_h3_progress_ingress_ngtcp2(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct proxy_pkt_io_ctx *pktx) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct proxy_pkt_io_ctx local_pktx; - CURLcode result = CURLE_OK; - - if(!ctx) - return CURLE_RECV_ERROR; - if(!data || !data->multi) - return CURLE_RECV_ERROR; - - if(!pktx) { - proxy_pktx_init(&local_pktx, cf, data); - pktx = &local_pktx; - } - else { - proxy_pktx_update_time(pktx, cf); - ngtcp2_path_storage_zero(&pktx->ps); - } - - result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); - if(result) - return result; - - if(ctx->q.sockfd == CURL_SOCKET_BAD) - return CURLE_RECV_ERROR; - - return vquic_recv_packets(cf, data, &ctx->q, 1000, - cf_ngtcp2_recv_pkts_proxy, pktx); -} - -/** - * Read a network packet to send from ngtcp2 into `buf`. - * Return number of bytes written or -1 with *err set. - */ -static CURLcode proxy_read_pkt_to_send(void *userp, - unsigned char *buf, size_t buflen, - size_t *pnread) -{ - struct proxy_pkt_io_ctx *x = userp; - struct cf_h3_proxy_ctx *proxy_ctx = x->cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - nghttp3_vec vec[16]; - nghttp3_ssize veccnt; - ngtcp2_ssize ndatalen; - uint32_t flags; - int64_t stream_id; - int fin; - ssize_t n; - - *pnread = 0; - veccnt = 0; - stream_id = -1; - fin = 0; - - /* ngtcp2 may want to put several frames from different streams into - * this packet. `NGTCP2_WRITE_STREAM_FLAG_MORE` tells it to do so. - * When `NGTCP2_ERR_WRITE_MORE` is returned, we *need* to make - * another iteration. - * When ngtcp2 is happy (because it has no other frame that would fit - * or it has nothing more to send), it returns the total length - * of the assembled packet. This may be 0 if there was nothing to send. */ - for(;;) { - - if(ctx->h3conn && ngtcp2_conn_get_max_data_left(ctx->qconn)) { - veccnt = nghttp3_conn_writev_stream(ctx->h3conn, &stream_id, &fin, vec, - CURL_ARRAYSIZE(vec)); - if(veccnt < 0) { - failf(x->data, "nghttp3_conn_writev_stream returned error: %s", - nghttp3_strerror((int)veccnt)); - cf_ngtcp2_proxy_h3_err_set(x->cf, x->data, (int)veccnt); - return CURLE_SEND_ERROR; - } - } - - flags = NGTCP2_WRITE_STREAM_FLAG_MORE | - (fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0); - n = ngtcp2_conn_writev_stream(ctx->qconn, &x->ps.path, - NULL, buf, buflen, - &ndatalen, flags, stream_id, - (const ngtcp2_vec *)vec, veccnt, x->ts); - if(n == 0) { - /* nothing to send */ - return CURLE_AGAIN; - } - else if(n < 0) { - switch(n) { - case NGTCP2_ERR_STREAM_DATA_BLOCKED: { - struct h3_proxy_stream_ctx *stream; - DEBUGASSERT(ndatalen == -1); - nghttp3_conn_block_stream(ctx->h3conn, stream_id); - CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] block quic flow", - stream_id); - stream = cf_ngtcp2_proxy_get_stream(ctx, stream_id); - if(stream) /* it might be not one of our h3 streams? */ - stream->quic_flow_blocked = TRUE; - n = 0; - break; - } - case NGTCP2_ERR_STREAM_SHUT_WR: - DEBUGASSERT(ndatalen == -1); - nghttp3_conn_shutdown_stream_write(ctx->h3conn, stream_id); - n = 0; - break; - case NGTCP2_ERR_WRITE_MORE: - /* ngtcp2 wants to send more. update the flow of the stream whose data - * is in the buffer and continue */ - DEBUGASSERT(ndatalen >= 0); - n = 0; - break; - default: - DEBUGASSERT(ndatalen == -1); - failf(x->data, "ngtcp2_conn_writev_stream returned error: %s", - ngtcp2_strerror((int)n)); - cf_ngtcp2_proxy_err_set(x->cf, x->data, (int)n); - return CURLE_SEND_ERROR; - } - } - - if(ndatalen >= 0) { - /* we add the amount of data bytes to the flow windows */ - int rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen); - if(rv) { - failf(x->data, "nghttp3_conn_add_write_offset returned error: %s", - nghttp3_strerror(rv)); - return CURLE_SEND_ERROR; - } - } - - if(n > 0) { - /* packet assembled, leave */ - *pnread = (size_t)n; - return CURLE_OK; - } - } -} - -static CURLcode proxy_h3_progress_egress_ngtcp2(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct proxy_pkt_io_ctx *pktx) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - size_t nread; - size_t max_payload_size, path_max_payload_size; - size_t pktcnt = 0; - size_t gsolen = 0; /* this disables gso until we have a clue */ - size_t send_quantum; - CURLcode result; - struct proxy_pkt_io_ctx local_pktx; - - if(!pktx) { - proxy_pktx_init(&local_pktx, cf, data); - pktx = &local_pktx; - } - else { - proxy_pktx_update_time(pktx, cf); - ngtcp2_path_storage_zero(&pktx->ps); - } - - result = vquic_flush(cf, data, &ctx->q); - if(result) { - if(result == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); - return CURLE_OK; - } - return result; - } - - /* In UDP, there is a maximum theoretical packet payload length and - * a minimum payload length that is "guaranteed" to work. - * To detect if this minimum payload can be increased, ngtcp2 sends - * now and then a packet payload larger than the minimum. It that - * is ACKed by the peer, both parties know that it works and - * the subsequent packets can use a larger one. - * This is called PMTUD (Path Maximum Transmission Unit Discovery). - * Since a PMTUD might be rejected right on send, we do not want it - * be followed by other packets of lesser size. Because those would - * also fail then. If we detect a PMTUD while buffering, we flush. - */ - max_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(ctx->qconn); - path_max_payload_size = - ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn); - send_quantum = ngtcp2_conn_get_send_quantum(ctx->qconn); - CURL_TRC_CF(data, cf, "egress, collect and send packets, quantum=%zu", - send_quantum); - for(;;) { - /* add the next packet to send, if any, to our buffer */ - result = Curl_bufq_sipn(&ctx->q.sendbuf, max_payload_size, - proxy_read_pkt_to_send, pktx, &nread); - if(result == CURLE_AGAIN) - break; - else if(result) - return result; - else { - size_t buflen = Curl_bufq_len(&ctx->q.sendbuf); - if((buflen >= send_quantum) || - ((buflen + gsolen) >= ctx->q.sendbuf.chunk_size)) - break; - DEBUGASSERT(nread > 0); - ++pktcnt; - if(pktcnt == 1) { - /* first packet in buffer. This is either of a known, "good" - * payload size or it is a PMTUD. We shall see. */ - gsolen = nread; - } - else if(nread > gsolen || - (gsolen > path_max_payload_size && nread != gsolen)) { - /* The added packet is a PMTUD *or* the one(s) before the - * added were PMTUD and the last one is smaller. - * Flush the buffer before the last add. */ - result = vquic_send_tail_split(cf, data, &ctx->q, - gsolen, nread, nread); - if(result) { - if(result == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); - return CURLE_OK; - } - return result; - } - pktcnt = 0; - } - else if(nread < gsolen) { - /* Reached capacity of our buffer *or* - * last add was shorter than the previous ones, flush */ - break; - } - } - } - - if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { - /* time to send */ - CURL_TRC_CF(data, cf, "egress, send collected %zu packets in %zu bytes", - pktcnt, Curl_bufq_len(&ctx->q.sendbuf)); - result = vquic_send(cf, data, &ctx->q, gsolen); - if(result) { - if(result == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); - return CURLE_OK; - } - return result; - } - proxy_pktx_update_time(pktx, cf); - ngtcp2_conn_update_pkt_tx_time(ctx->qconn, pktx->ts); - } - return CURLE_OK; -} - -static CURLcode cf_ngtcp2_proxy_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data, bool *done) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct cf_call_data save; - struct proxy_pkt_io_ctx pktx; - CURLcode result = CURLE_OK; - - if(cf->shutdown || !ctx->qconn) { - *done = TRUE; - return CURLE_OK; - } - - if(!cf->next) { - Curl_bufq_reset(&ctx->q.sendbuf); - *done = TRUE; - return CURLE_OK; - } - - CF_DATA_SAVE(save, cf, data); - *done = FALSE; - proxy_pktx_init(&pktx, cf, data); - - if(!ctx->shutdown_started) { - char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; - ngtcp2_ssize nwritten; - - if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { - CURL_TRC_CF(data, cf, "shutdown, flushing sendbuf"); - result = proxy_h3_progress_egress_ngtcp2(cf, data, &pktx); - if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { - CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); - result = CURLE_OK; - goto out; - } - else if(result) { - CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); - *done = TRUE; - goto out; - } - } - - DEBUGASSERT(Curl_bufq_is_empty(&ctx->q.sendbuf)); - ctx->shutdown_started = TRUE; - nwritten = ngtcp2_conn_write_connection_close( - ctx->qconn, NULL, /* path */ - NULL, /* pkt_info */ - (uint8_t *)buffer, sizeof(buffer), - &ctx->last_error, pktx.ts); - CURL_TRC_CF(data, cf, "start shutdown(err_type=%d, err_code=%" - PRIu64 ") -> %zd", ctx->last_error.type, - ctx->last_error.error_code, (ssize_t)nwritten); - /* there are cases listed in ngtcp2 documentation where this call - * may fail. Since we are doing a connection shutdown as graceful - * as we can, such an error is ignored here. */ - if(nwritten > 0) { - /* Ignore amount written. sendbuf was empty and has always room for - * NGTCP2_MAX_UDP_PAYLOAD_SIZE. It can only completely fail, in which - * case `result` is set non zero. */ - size_t n; - result = Curl_bufq_write(&ctx->q.sendbuf, (const unsigned char *)buffer, - (size_t)nwritten, &n); - if(result) { - CURL_TRC_CF(data, cf, "error %d adding shutdown packets to sendbuf, " - "aborting shutdown", result); - goto out; - } - - ctx->q.no_gso = TRUE; - ctx->q.gsolen = (size_t)nwritten; - ctx->q.split_len = 0; - } - } - - if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { - CURL_TRC_CF(data, cf, "shutdown, flushing egress"); - result = vquic_flush(cf, data, &ctx->q); - if(result == CURLE_AGAIN) { - CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); - result = CURLE_OK; - goto out; - } - else if(result) { - CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); - *done = TRUE; - goto out; - } - } - - if(Curl_bufq_is_empty(&ctx->q.sendbuf)) { - /* Sent everything off. ngtcp2 seems to have no support for graceful - * shutdowns. We are done. */ - CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); - *done = TRUE; - result = CURLE_OK; - } -out: - CF_DATA_RESTORE(cf, save); - return result; -} - -static void cf_ngtcp2_proxy_conn_close(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - bool done; - cf_ngtcp2_proxy_shutdown(cf, data, &done); -} - -static void cf_ngtcp2_proxy_close(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - if(ctx && ctx->qconn) { - cf_ngtcp2_proxy_conn_close(cf, data); - cf_ngtcp2_proxy_ctx_close(ctx); - CURL_TRC_CF(data, cf, "close"); - } - cf->connected = FALSE; - CF_DATA_RESTORE(cf, save); -} - -static void cf_ngtcp2_proxy_stream_close(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h3_proxy_stream_ctx *stream) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - DEBUGASSERT(data); - DEBUGASSERT(stream); - - if(stream->id == proxy_ctx->tunnel.stream_id) { - proxy_ctx->tunnel.stream = NULL; - proxy_ctx->tunnel.closed = TRUE; - } - - if(ctx->h3conn) - nghttp3_conn_set_stream_user_data(ctx->h3conn, stream->id, NULL); - if(ctx->qconn) - ngtcp2_conn_set_stream_user_data(ctx->qconn, stream->id, NULL); - - if(!stream->closed && ctx->qconn && ctx->h3conn) { - CURLcode result; - - stream->closed = TRUE; - (void)ngtcp2_conn_shutdown_stream(ctx->qconn, 0, stream->id, - NGHTTP3_H3_REQUEST_CANCELLED); - result = proxy_h3_progress_egress_ngtcp2(cf, data, NULL); - if(result) - CURL_TRC_CF(data, cf, "[%" PRId64 "] cancel stream -> %d", - stream->id, result); - } -} - -/** - * Connection maintenance like timeouts on packet ACKs etc. are done by us, not - * the OS like for TCP. POLL events on the socket therefore are not - * sufficient. - * ngtcp2 tells us when it wants to be invoked again. We handle that via - * the `Curl_expire()` mechanisms. - */ -static CURLcode check_and_set_expiry_ngtcp2(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct proxy_pkt_io_ctx *pktx) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct proxy_pkt_io_ctx local_pktx; - ngtcp2_tstamp expiry; - - if(!ctx) - return CURLE_OK; - - if(!pktx) { - proxy_pktx_init(&local_pktx, cf, data); - pktx = &local_pktx; - } - else { - proxy_pktx_update_time(pktx, cf); - } - - expiry = ngtcp2_conn_get_expiry(ctx->qconn); - if(expiry != UINT64_MAX) { - if(expiry <= pktx->ts) { - CURLcode result; - int rv = ngtcp2_conn_handle_expiry(ctx->qconn, pktx->ts); - if(rv) { - failf(data, "ngtcp2_conn_handle_expiry returned error: %s", - ngtcp2_strerror(rv)); - cf_ngtcp2_proxy_err_set(cf, data, rv); - return CURLE_SEND_ERROR; - } - result = proxy_h3_progress_ingress_ngtcp2(cf, data, pktx); - if(result) - return result; - result = proxy_h3_progress_egress_ngtcp2(cf, data, pktx); - if(result) - return result; - /* ask again, things might have changed */ - expiry = ngtcp2_conn_get_expiry(ctx->qconn); - } - - if(expiry > pktx->ts) { - ngtcp2_duration timeout = expiry - pktx->ts; - if(timeout % NGTCP2_MILLISECONDS) { - timeout += NGTCP2_MILLISECONDS; - } - Curl_expire(data, (timediff_t)(timeout / NGTCP2_MILLISECONDS), - EXPIRE_QUIC); - } - } - return CURLE_OK; -} - -static ssize_t proxy_recv_closed_stream(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h3_proxy_stream_ctx *stream, - CURLcode *err) +static ssize_t cf_h3_proxy_recv_closed_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + CURLcode *err) { ssize_t nread = -1; *err = CURLE_OK; @@ -2164,27 +698,10 @@ out: return nread; } -static struct h3_proxy_stream_ctx *h3_proxy_resolve_send_stream( - struct cf_h3_proxy_ctx *proxy_ctx, - struct cf_ngtcp2_proxy_ctx *ctx, - struct Curl_easy *data) -{ - struct h3_proxy_stream_ctx *stream = H3_PROXY_STREAM_CTX(ctx, data); - - if(stream) - return stream; - - /* send can be driven by a different easy handle during shutdown */ - if(proxy_ctx->tunnel.stream && !proxy_ctx->tunnel.closed) { - return proxy_ctx->tunnel.stream; - } - return NULL; -} - -static CURLcode h3_proxy_sendbuf_add(struct Curl_easy *data, - struct h3_proxy_stream_ctx *stream, - const uint8_t *buf, size_t len, - size_t *pnwritten) +static CURLcode cf_h3_proxy_sendbuf_add(struct Curl_easy *data, + struct h3_stream_ctx *stream, + const uint8_t *buf, size_t len, + size_t *pnwritten) { CURLcode result; *pnwritten = 0; @@ -2199,18 +716,18 @@ static CURLcode cf_h3_proxy_send(struct Curl_cfilter *cf, const uint8_t *buf, size_t len, bool eos, size_t *pnwritten) { - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct h3_proxy_stream_ctx *stream = NULL; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + struct h3_stream_ctx *stream = NULL; struct cf_call_data save; - struct proxy_pkt_io_ctx pktx; + struct cf_ngtcp2_io_ctx pktx; CURLcode result = CURLE_OK; CF_DATA_SAVE(save, cf, data); DEBUGASSERT(cf->connected); DEBUGASSERT(ctx->qconn); DEBUGASSERT(ctx->h3conn); - proxy_pktx_init(&pktx, cf, data); + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); *pnwritten = 0; /* handshake verification failed in callback, do not send anything */ @@ -2220,17 +737,17 @@ static CURLcode cf_h3_proxy_send(struct Curl_cfilter *cf, } (void)eos; /* use for stream EOF and block handling */ - result = proxy_h3_progress_ingress_ngtcp2(cf, data, &pktx); + result = Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx); if(result) goto out; - stream = h3_proxy_resolve_send_stream(proxy_ctx, ctx, data); - if(!stream) { + if(pctx->tunnel.closed) { result = CURLE_SEND_ERROR; goto denied; } - if(proxy_ctx->tunnel.closed) { + stream = pctx->tunnel.stream; + if(!stream) { result = CURLE_SEND_ERROR; goto denied; } @@ -2254,7 +771,7 @@ static CURLcode cf_h3_proxy_send(struct Curl_cfilter *cf, goto out; } else { - result = h3_proxy_sendbuf_add(data, stream, buf, len, pnwritten); + result = cf_h3_proxy_sendbuf_add(data, stream, buf, len, pnwritten); CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to " "sendbuf(len=%zu) -> %d, %zu", stream->id, len, result, *pnwritten); @@ -2267,11 +784,11 @@ static CURLcode cf_h3_proxy_send(struct Curl_cfilter *cf, ctx->earlydata_skip += *pnwritten; DEBUGASSERT(!result); - result = proxy_h3_progress_egress_ngtcp2(cf, data, &pktx); + result = Curl_cf_ngtcp2_progress_egress(cf, data, &pktx); out: result = Curl_1st_fatal(result, - check_and_set_expiry_ngtcp2(cf, data, &pktx)); + Curl_cf_ngtcp2_cmn_set_expiry(cf, data, &pktx)); denied: CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %d, %zu", stream ? stream->id : -1, len, result, *pnwritten); @@ -2284,11 +801,11 @@ static CURLcode cf_h3_proxy_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, size_t *pnread) { - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct h3_proxy_stream_ctx *stream = H3_PROXY_STREAM_CTX(ctx, data); + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + struct h3_stream_ctx *stream = pctx->tunnel.stream; struct cf_call_data save; - struct proxy_pkt_io_ctx pktx; + struct cf_ngtcp2_io_ctx pktx; CURLcode result = CURLE_OK; CF_DATA_SAVE(save, cf, data); @@ -2304,26 +821,26 @@ static CURLcode cf_h3_proxy_recv(struct Curl_cfilter *cf, goto denied; } - proxy_pktx_init(&pktx, cf, data); + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); if(!stream || ctx->shutdown_started) { result = CURLE_RECV_ERROR; goto out; } - if(!Curl_bufq_is_empty(&proxy_ctx->inbufq)) { - result = Curl_bufq_cread(&proxy_ctx->inbufq, buf, len, pnread); + if(!Curl_bufq_is_empty(&pctx->tunnel.recvbuf)) { + result = Curl_bufq_cread(&pctx->tunnel.recvbuf, buf, len, pnread); if(result) goto out; } - result = proxy_h3_progress_ingress_ngtcp2(cf, data, &pktx); + result = Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx); if(result) goto out; /* inbufq had nothing before, maybe after progressing ingress? */ - if(!*pnread && !Curl_bufq_is_empty(&proxy_ctx->inbufq)) { - result = Curl_bufq_cread(&proxy_ctx->inbufq, buf, len, pnread); + if(!*pnread && !Curl_bufq_is_empty(&pctx->tunnel.recvbuf)) { + result = Curl_bufq_cread(&pctx->tunnel.recvbuf, buf, len, pnread); if(result) { CURL_TRC_CF(data, cf, "[%" PRId64 "] read inbufq(len=%zu) -> %zu, %d", stream->id, len, *pnread, result); @@ -2337,12 +854,13 @@ static CURLcode cf_h3_proxy_recv(struct Curl_cfilter *cf, else { if(stream->xfer_result) { CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id); - cf_ngtcp2_proxy_stream_close(cf, data, stream); + Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); result = stream->xfer_result; goto out; } else if(stream->closed) { - ssize_t nread = proxy_recv_closed_stream(cf, data, stream, &result); + ssize_t nread = + cf_h3_proxy_recv_closed_stream(cf, data, stream, &result); if(nread > 0) *pnread = (size_t)nread; goto out; @@ -2352,9 +870,9 @@ static CURLcode cf_h3_proxy_recv(struct Curl_cfilter *cf, out: result = Curl_1st_fatal(result, - proxy_h3_progress_egress_ngtcp2(cf, data, &pktx)); + Curl_cf_ngtcp2_progress_egress(cf, data, &pktx)); result = Curl_1st_fatal(result, - check_and_set_expiry_ngtcp2(cf, data, &pktx)); + Curl_cf_ngtcp2_cmn_set_expiry(cf, data, &pktx)); denied: CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(len=%zu) -> %d, %zu", stream ? stream->id : -1, len, result, *pnread); @@ -2362,51 +880,42 @@ denied: return result; } -static void proxy_h3_submit(int64_t *pstream_id, - struct Curl_cfilter *cf, - struct Curl_easy *data, - struct httpreq *req, - CURLcode *err) +static CURLcode cf_h3_proxy_submit(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_tunnel_stream *ts, + struct httpreq *req) { - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct h3_proxy_stream_ctx *stream = NULL; - + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + struct h3_stream_ctx *stream = NULL; struct dynhds h2_headers; nghttp3_nv *nva = NULL; size_t nheader; - int rc = 0; unsigned int i; nghttp3_data_reader reader; nghttp3_data_reader *preader = NULL; + CURLcode result; Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); - *err = Curl_http_req_to_h2(&h2_headers, req, data); - if(*err) + result = Curl_http_req_to_h2(&h2_headers, req, data); + if(result) goto out; - *err = h3_proxy_data_setup(cf, data); - if(*err) + result = Curl_cf_ngtcp2_h3_stream_setup(cf, data); + if(result) goto out; - - if(!ctx) { - *err = CURLE_FAILED_INIT; - goto out; - } - - stream = H3_PROXY_STREAM_CTX(ctx, data); - + stream = H3_STREAM_CTX(ctx, data); DEBUGASSERT(stream); if(!stream) { - *err = CURLE_FAILED_INIT; + result = CURLE_FAILED_INIT; goto out; } nheader = Curl_dynhds_count(&h2_headers); nva = curlx_malloc(sizeof(nghttp3_nv) * nheader); if(!nva) { - *err = CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; goto out; } @@ -2428,574 +937,113 @@ static void proxy_h3_submit(int64_t *pstream_id, rv = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, data); if(rv) { failf(data, "cannot get bidi streams: %s", ngtcp2_strerror(rv)); - *err = CURLE_SEND_ERROR; + result = CURLE_SEND_ERROR; goto out; } stream->id = sid; ++ctx->used_bidi_streams; - /* Set stream user data in ngtcp2 connection for callbacks */ - rv = ngtcp2_conn_set_stream_user_data(ctx->qconn, sid, data); - if(rv) { - failf(data, "cannot set stream user data: %s", ngtcp2_strerror(rv)); - *err = CURLE_SEND_ERROR; - goto out; - } - proxy_ctx->tunnel.stream = stream; + /* Do NOT set `data` as stream user data. The transfer `data` may + * get cleaned up long before the tunnel goes down. */ + ts->stream = stream; CURL_TRC_CF(data, cf, "[%" PRId64 "] opened bidi stream", sid); } /* CONNECT-UDP request stream remains open for capsules, no fixed EOF. */ - stream->upload_left = -1; stream->send_closed = 0; - reader.read_data = cb_h3_read_data_for_tunnel_stream; + reader.read_data = cb_h3_tunnel_read_data; preader = &reader; - rc = nghttp3_conn_submit_request(ctx->h3conn, H3_STREAM_ID(stream), + rc = nghttp3_conn_submit_request(ctx->h3conn, stream->id, nva, nheader, preader, data); if(rc) { switch(rc) { case NGHTTP3_ERR_CONN_CLOSING: CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send, " - "connection is closing", H3_STREAM_ID(stream)); + "connection is closing", stream->id); break; default: CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send -> %d (%s)", - H3_STREAM_ID(stream), rc, nghttp3_strerror(rc)); + stream->id, rc, nghttp3_strerror(rc)); break; } - *err = CURLE_SEND_ERROR; + result = CURLE_SEND_ERROR; goto out; } if(Curl_trc_is_verbose(data)) { CURL_TRC_CF(data, cf, "[H3-PROXY] [%" PRId64 "] OPENED stream " - "for %s", H3_STREAM_ID(stream), + "for %s", stream->id, Curl_bufref_ptr(&data->state.url)); } out: curlx_free(nva); Curl_dynhds_free(&h2_headers); - if(*err == CURLE_OK) { - *pstream_id = H3_STREAM_ID(stream); - } -} - -static bool cf_h3_proxy_is_alive(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *input_pending) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - bool alive = FALSE; - const ngtcp2_transport_params *rp; - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - *input_pending = FALSE; - - if(!ctx || !ctx->qconn || ctx->shutdown_started) - goto out; - if(proxy_ctx->tunnel.closed) - goto out; - - /* We do not announce a max idle timeout, but when the peer does - * it closes the connection when it expires. */ - rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); - if(rp && rp->max_idle_timeout) { - timediff_t idletime_ms = - curlx_ptimediff_ms(Curl_pgrs_now(data), &ctx->q.last_io); - if(idletime_ms > 0) { - uint64_t max_idle_ms = - (uint64_t)(rp->max_idle_timeout / NGTCP2_MILLISECONDS); - if((uint64_t)idletime_ms > max_idle_ms) - goto out; - } - } - - if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) - goto out; - - alive = TRUE; - if(*input_pending) { - CURLcode result; - /* This happens before we have sent off a request and the connection is - not in use by any other transfer, there should not be any data here, - only "protocol frames" */ - *input_pending = FALSE; - if(!data || !data->multi) { - alive = FALSE; - goto out; - } - result = proxy_h3_progress_ingress_ngtcp2(cf, data, NULL); - CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); - alive = result ? FALSE : TRUE; - } - -out: - CF_DATA_RESTORE(cf, save); - return alive; -} - -static CURLcode cf_ngtcp2_proxy_query(struct Curl_cfilter *cf, - struct Curl_easy *data, - int query, int *pres1, void *pres2) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct cf_call_data save; - - if(!ctx) - return cf->next ? - cf->next->cft->query(cf->next, data, query, pres1, pres2) : - CURLE_UNKNOWN_OPTION; - - switch(query) { - case CF_QUERY_MAX_CONCURRENT: { - DEBUGASSERT(pres1); - CF_DATA_SAVE(save, cf, data); - /* Set after transport params arrived and continually updated - * by callback. QUIC counts the number over the lifetime of the - * connection, ever increasing. - * We count the *open* transfers plus the budget for new ones. */ - if(!ctx->qconn || ctx->shutdown_started) { - *pres1 = 0; - } - else if(ctx->max_bidi_streams) { - uint64_t avail_bidi_streams = 0; - uint64_t max_streams = cf->conn->attached_xfers; - if(ctx->max_bidi_streams > ctx->used_bidi_streams) - avail_bidi_streams = ctx->max_bidi_streams - ctx->used_bidi_streams; - max_streams += avail_bidi_streams; - *pres1 = (max_streams > INT_MAX) ? INT_MAX : (int)max_streams; - } - else /* transport params not arrived yet? take our default. */ - *pres1 = (int)Curl_multi_max_concurrent_streams(data->multi); - CURL_TRC_CF(data, cf, "query conn[%" FMT_OFF_T "]: " - "MAX_CONCURRENT -> %d (%u in use)", - cf->conn->connection_id, *pres1, cf->conn->attached_xfers); - CF_DATA_RESTORE(cf, save); - return CURLE_OK; - } - case CF_QUERY_CONNECT_REPLY_MS: - if(ctx->q.got_first_byte) { - timediff_t ms = curlx_ptimediff_ms(&ctx->q.first_byte_at, - &ctx->started_at); - *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; - } - else - *pres1 = -1; - return CURLE_OK; - case CF_QUERY_TIMER_CONNECT: { - struct curltime *when = pres2; - if(ctx->q.got_first_byte) - *when = ctx->q.first_byte_at; - return CURLE_OK; - } - case CF_QUERY_TIMER_APPCONNECT: { - struct curltime *when = pres2; - if(cf->connected) - *when = ctx->handshake_at; - return CURLE_OK; - } - case CF_QUERY_HTTP_VERSION: - *pres1 = 30; - return CURLE_OK; - case CF_QUERY_SSL_INFO: - case CF_QUERY_SSL_CTX_INFO: { - struct curl_tlssessioninfo *info = pres2; - if(Curl_vquic_tls_get_ssl_info(&ctx->tls, - (query == CF_QUERY_SSL_CTX_INFO), info)) - return CURLE_OK; - break; - } - case CF_QUERY_ALPN_NEGOTIATED: { - const char **palpn = pres2; - DEBUGASSERT(palpn); - *palpn = cf->connected ? "h3" : NULL; - return CURLE_OK; - } - default: - break; - } - return cf->next ? - cf->next->cft->query(cf->next, data, query, pres1, pres2) : - CURLE_UNKNOWN_OPTION; -} - -static CURLcode cf_ngtcp2_proxy_adjust_pollset(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct easy_pollset *ps) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - bool want_recv, want_send; - CURLcode result = CURLE_OK; - - if(!ctx->qconn) - return CURLE_OK; - - Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); - if(!want_send && !Curl_bufq_is_empty(&ctx->q.sendbuf)) - want_send = TRUE; - - if(want_recv || want_send) { - struct h3_proxy_stream_ctx *stream = H3_PROXY_STREAM_CTX(ctx, data); - struct cf_call_data save; - bool c_exhaust, s_exhaust; - - CF_DATA_SAVE(save, cf, data); - c_exhaust = want_send && - (!ngtcp2_conn_get_cwnd_left(ctx->qconn) || - !ngtcp2_conn_get_max_data_left(ctx->qconn)); - s_exhaust = want_send && stream && H3_STREAM_ID(stream) >= 0 && - stream->quic_flow_blocked; - want_recv = (want_recv || c_exhaust || s_exhaust); - want_send = (!s_exhaust && want_send) || - !Curl_bufq_is_empty(&ctx->q.sendbuf); - - result = Curl_pollset_set(data, ps, ctx->q.sockfd, want_recv, want_send); - CF_DATA_RESTORE(cf, save); - } return result; } -static CURLcode cf_h3_proxy_query(struct Curl_cfilter *cf, - struct Curl_easy *data, - int query, int *pres1, void *pres2) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - - if(!proxy_ctx) - return cf->next ? - cf->next->cft->query(cf->next, data, query, pres1, pres2) : - CURLE_UNKNOWN_OPTION; - return cf_ngtcp2_proxy_query(cf, data, query, pres1, pres2); -} - static CURLcode cf_h3_proxy_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, struct easy_pollset *ps) { - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; + struct cf_h3_proxy_ctx *pctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; + bool want_recv, want_send; + CURLcode result = CURLE_OK; + curl_socket_t sock = (ctx->q.sockfd != CURL_SOCKET_BAD) ? + ctx->q.sockfd : Curl_conn_cf_get_socket(cf, data); - if(!proxy_ctx) - return cf->next ? - cf->next->cft->adjust_pollset(cf->next, data, ps) : - CURLE_OK; - return cf_ngtcp2_proxy_adjust_pollset(cf, data, ps); + if(!ctx->qconn || !pctx->tunnel.stream || (sock == CURL_SOCKET_BAD)) + return CURLE_OK; + + Curl_pollset_check(data, ps, sock, &want_recv, &want_send); + + if(want_recv || want_send || !Curl_bufq_is_empty(&ctx->q.sendbuf)) { + struct h3_stream_ctx *stream = pctx->tunnel.stream; + bool c_exhaust, s_exhaust; + + c_exhaust = want_send && + (!ngtcp2_conn_get_cwnd_left(ctx->qconn) || + !ngtcp2_conn_get_max_data_left(ctx->qconn)); + s_exhaust = want_send && stream && stream->id >= 0 && + stream->quic_flow_blocked; + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + !Curl_bufq_is_empty(&ctx->q.sendbuf); + + result = Curl_pollset_set(data, ps, sock, want_recv, want_send); + } + return result; } static bool cf_h3_proxy_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - if(!proxy_ctx) - return cf->next ? - cf->next->cft->has_data_pending(cf->next, data) : FALSE; - if(!Curl_bufq_is_empty(&proxy_ctx->inbufq)) + struct cf_h3_proxy_ctx *pctx = cf->ctx; + if(!Curl_bufq_is_empty(&pctx->tunnel.recvbuf)) return TRUE; return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE; } -#ifdef USE_OPENSSL -static int proxy_quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +static CURLcode cf_h3_proxy_submit_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_tunnel_stream *ts) { - ngtcp2_crypto_conn_ref *cref; - struct Curl_cfilter *cf; - struct cf_ngtcp2_proxy_ctx *ctx; - struct cf_h3_proxy_ctx *proxy_ctx; - struct Curl_easy *data; - - cref = (ngtcp2_crypto_conn_ref *)SSL_get_app_data(ssl); - cf = cref ? cref->user_data : NULL; - proxy_ctx = cf ? cf->ctx : NULL; - ctx = proxy_ctx ? proxy_ctx->ngtcp2_ctx : NULL; - data = cf ? CF_DATA_CURRENT(cf) : NULL; - if(cf && data && ctx) { - unsigned char *quic_tp = NULL; - size_t quic_tp_len = 0; -#ifdef HAVE_OPENSSL_EARLYDATA - ngtcp2_ssize tplen; - uint8_t tpbuf[256]; - - tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, - sizeof(tpbuf)); - if(tplen < 0) - CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", - ngtcp2_strerror((int)tplen)); - else { - quic_tp = (unsigned char *)tpbuf; - quic_tp_len = (size_t)tplen; - } -#endif - Curl_ossl_add_session(cf, data, ctx->peer.scache_key, ssl_sessionid, - SSL_version(ssl), "h3", quic_tp, quic_tp_len); - } - return 0; -} -#endif /* USE_OPENSSL */ - -static CURLcode cf_ngtcp2_proxy_tls_ctx_setup(struct Curl_cfilter *cf, - struct Curl_easy *data, - void *user_data) -{ - struct curl_tls_ctx *ctx = user_data; - -#ifdef USE_OPENSSL -#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL) - if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ossl.ssl_ctx) - != 0) { - failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); - return CURLE_FAILED_INIT; - } -#elif defined(OPENSSL_QUIC_API2) - /* nothing to do */ -#else - if(ngtcp2_crypto_quictls_configure_client_context(ctx->ossl.ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); - return CURLE_FAILED_INIT; - } -#endif /* !OPENSSL_IS_AWSLC && !OPENSSL_IS_BORINGSSL */ - if(Curl_ssl_scache_use(cf, data)) { - SSL_CTX_set_session_cache_mode(ctx->ossl.ssl_ctx, - SSL_SESS_CACHE_CLIENT | - SSL_SESS_CACHE_NO_INTERNAL); - SSL_CTX_sess_set_new_cb(ctx->ossl.ssl_ctx, proxy_quic_ossl_new_session_cb); - } - -#else -#error "ngtcp2 TLS backend not configured" -#endif /* USE_OPENSSL */ - - return CURLE_OK; -} - -static CURLcode cf_ngtcp2_proxy_on_session_reuse(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct alpn_spec *alpns, - struct Curl_ssl_session *scs, - bool *do_early_data) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - CURLcode result = CURLE_OK; - - *do_early_data = FALSE; -#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) - ctx->earlydata_max = scs->earlydata_max; -#endif -#ifdef USE_GNUTLS - ctx->earlydata_max = - gnutls_record_get_max_early_data_size(ctx->tls.gtls.session); -#endif -#ifdef USE_WOLFSSL -#ifdef WOLFSSL_EARLY_DATA - ctx->earlydata_max = scs->earlydata_max; -#else - ctx->earlydata_max = 0; -#endif /* WOLFSSL_EARLY_DATA */ -#endif -#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ - (defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA)) - if(!ctx->earlydata_max) { - CURL_TRC_CF(data, cf, "SSL session does not allow earlydata"); - } - else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) { - CURL_TRC_CF(data, cf, "SSL session from different ALPN, no early data"); - } - else if(!scs->quic_tp || !scs->quic_tp_len) { - CURL_TRC_CF(data, cf, "no 0RTT transport parameters, no early data"); - } - else { - int rv; - rv = ngtcp2_conn_decode_and_set_0rtt_transport_params( - ctx->qconn, (const uint8_t *)scs->quic_tp, scs->quic_tp_len); - if(rv) - CURL_TRC_CF(data, cf, "no early data, failed to set 0RTT transport " - "parameters: %s", ngtcp2_strerror(rv)); - else { - infof(data, "SSL session allows %zu bytes of early data, " - "reusing ALPN '%s'", ctx->earlydata_max, scs->alpn); - result = cf_ngtcp2_h3conn_init(cf, data); - if(!result) { - ctx->use_earlydata = TRUE; - proxy_ctx->connected = TRUE; - *do_early_data = TRUE; - } - } - } -#else /* not supported in the TLS backend */ - (void)data; - (void)ctx; - (void)scs; - (void)alpns; -#endif - return result; -} - -static CURLcode cf_h3_proxy_ctx_init(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = NULL; - int rc; - int rv; - CURLcode result = CURLE_OK; - const struct Curl_sockaddr_ex *sockaddr = NULL; - int qfd; - static const struct alpn_spec ALPN_SPEC_H3 = { { "h3", "h3-29" }, 2 }; - struct proxy_pkt_io_ctx pktx; - - ctx = curlx_calloc(1, sizeof(struct cf_ngtcp2_proxy_ctx)); - if(!ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - cf_ngtcp2_proxy_ctx_init(ctx); - - memset(&proxy_ctx->tunnel, 0, sizeof(proxy_ctx->tunnel)); - - Curl_bufq_init2(&proxy_ctx->inbufq, PROXY_H3_STREAM_CHUNK_SIZE, - PROXY_H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - - result = h3_tunnel_stream_init(&proxy_ctx->tunnel, proxy_ctx->dest); - if(result) - goto out; - - DEBUGASSERT(ctx->initialized); - ctx->started_at = *Curl_pgrs_now(data); - - /* Initialize connection IDs BEFORE creating the connection */ - ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; - result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN); - if(result) - goto out; - - ctx->scid.datalen = NGTCP2_MAX_CIDLEN; - result = Curl_rand(data, ctx->scid.data, NGTCP2_MAX_CIDLEN); - if(result) - goto out; - - (void)Curl_qlogdir(data, ctx->scid.data, NGTCP2_MAX_CIDLEN, &qfd); - ctx->qlogfd = qfd; /* -1 if failure above */ - - result = CURLE_QUIC_CONNECT_ERROR; - if(!cf->next) { - CURL_TRC_CF(data, cf, "h3_proxy_ctx_init: no lower filter"); - goto out; - } - ctx->q.sockfd = Curl_conn_cf_get_socket(cf->next, data); - if(ctx->q.sockfd == CURL_SOCKET_BAD) - goto out; - /* Get remote address from the socket filter below */ - if(cf->next->cft->query(cf->next, data, CF_QUERY_REMOTE_ADDR, NULL, - CURL_UNCONST(&sockaddr))) - goto out; - if(!sockaddr) - goto out; - ctx->q.local_addrlen = sizeof(ctx->q.local_addr); - rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, - &ctx->q.local_addrlen); - if(rv == -1) - goto out; - - /* Initialize vquic context BEFORE proxy_pktx_init which needs it */ - result = vquic_ctx_init(data, &ctx->q); - if(result) - goto out; - - /* Set ngtcp2_ctx in proxy_ctx BEFORE proxy_pktx_init which accesses it */ - proxy_ctx->ngtcp2_ctx = ctx; - - /* Now we can safely initialize pktx and settings */ - proxy_pktx_init(&pktx, cf, data); - quic_settings_proxy(ctx, data, &pktx); - - ngtcp2_addr_init(&ctx->connected_path.local, - (struct sockaddr *)&ctx->q.local_addr, - ctx->q.local_addrlen); - ngtcp2_addr_init(&ctx->connected_path.remote, - &sockaddr->curl_sa_addr, (socklen_t)sockaddr->addrlen); - - rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, - &ctx->connected_path, - NGTCP2_PROTO_VER_V1, &ngtcp2_proxy_callbacks, - &ctx->settings, &ctx->transport_params, - Curl_ngtcp2_mem(), cf); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto out; - } - - ctx->conn_ref.get_conn = proxy_get_conn; - ctx->conn_ref.user_data = cf; - - result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, &ALPN_SPEC_H3, - cf_ngtcp2_proxy_tls_ctx_setup, &ctx->tls, - &ctx->conn_ref, - cf_ngtcp2_proxy_on_session_reuse); - if(result) - goto out; - -#if defined(USE_OPENSSL) && defined(OPENSSL_QUIC_API2) - if(ngtcp2_crypto_ossl_ctx_new(&ctx->ossl_ctx, ctx->tls.ossl.ssl) != 0) { - failf(data, "ngtcp2_crypto_ossl_ctx_new failed"); - result = CURLE_FAILED_INIT; - goto out; - } - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ossl_ctx); - if(ngtcp2_crypto_ossl_configure_client_session(ctx->tls.ossl.ssl) != 0) { - failf(data, "ngtcp2_crypto_ossl_configure_client_session failed"); - result = CURLE_FAILED_INIT; - goto out; - } -#elif defined(USE_OPENSSL) - SSL_set_quic_use_legacy_codepoint(ctx->tls.ossl.ssl, 0); - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ossl.ssl); -#else -#error "ngtcp2 TLS backend not defined" -#endif /* USE_OPENSSL */ - - ngtcp2_ccerr_default(&ctx->last_error); - - proxy_ctx->connected = FALSE; - -out: - if(result) { - if(ctx) { - proxy_ctx->ngtcp2_ctx = NULL; /* Clear before freeing on error */ - cf_ngtcp2_proxy_ctx_free(ctx); - } - } - CURL_TRC_CF(data, cf, "QUIC tls init -> %d", result); - return result; -} - -static CURLcode h3_submit_CONNECT(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h3_tunnel_stream *ts) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; CURLcode result; struct httpreq *req = NULL; result = Curl_http_proxy_create_tunnel_request(&req, cf, data, - proxy_ctx->dest, + ts->peer, PROXY_HTTP_V3, - (bool)proxy_ctx->udp_tunnel); - if(result) - goto out; - result = Curl_creader_set_null(data); - if(result) - goto out; + (bool)ts->udp); + if(!result) + result = Curl_creader_set_null(data); + if(!result) + result = cf_h3_proxy_submit(cf, data, ts, req); - proxy_h3_submit(&ts->stream_id, cf, data, req, &result); - -out: if(req) Curl_http_req_free(req); if(result) @@ -3003,173 +1051,54 @@ out: return result; } -static CURLcode h3_proxy_inspect_response(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h3_tunnel_stream *ts) +static CURLcode cf_h3_proxy_inspect_response(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_tunnel_stream *ts) { - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; + struct cf_h3_proxy_ctx *pctx = cf->ctx; proxy_inspect_result res; CURLcode result; result = Curl_http_proxy_inspect_tunnel_response( - cf, data, ts->resp, (bool)proxy_ctx->udp_tunnel, &res); + cf, data, ts->resp, (bool)pctx->tunnel.udp, &res); if(result) return result; switch(res) { case PROXY_INSPECT_OK: - h3_tunnel_go_state(cf, ts, H3_TUNNEL_ESTABLISHED, data, - (bool)proxy_ctx->udp_tunnel); + h3_tunnel_go_state(cf, ts, H3_TUNNEL_ESTABLISHED, data); break; case PROXY_INSPECT_FAILED: - h3_tunnel_go_state(cf, ts, H3_TUNNEL_FAILED, data, - (bool)proxy_ctx->udp_tunnel); + h3_tunnel_go_state(cf, ts, H3_TUNNEL_FAILED, data); result = CURLE_COULDNT_CONNECT; break; case PROXY_INSPECT_AUTH_RETRY: - h3_tunnel_go_state(cf, ts, H3_TUNNEL_INIT, data, - (bool)proxy_ctx->udp_tunnel); + h3_tunnel_go_state(cf, ts, H3_TUNNEL_INIT, data); break; } return result; } -static CURLcode cf_h3_proxy_quic_connect(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *done) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_call_data save; - CURLcode result = CURLE_OK; - struct proxy_pkt_io_ctx pktx; - - if(proxy_ctx->connected) { - *done = TRUE; - return CURLE_OK; - } - - /* Connect the sub-chain (UDP via happy eyeballs) */ - if(cf->next && !cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, done); - if(result || !*done) - return result; - } - - *done = FALSE; - if(!proxy_ctx->dest) { - Curl_peer_link(&proxy_ctx->dest, - Curl_conn_get_destination(cf->conn, cf->sockindex)); - } - - if(!proxy_ctx->ngtcp2_ctx) { - result = cf_h3_proxy_ctx_init(cf, data); - if(result) - return result; - } - - /* Initialize pktx AFTER ensuring ngtcp2_ctx exists */ - proxy_pktx_init(&pktx, cf, data); - - CF_DATA_SAVE(save, cf, data); - - if(!proxy_ctx->ngtcp2_ctx->qconn) { - proxy_ctx->ngtcp2_ctx->started_at = *Curl_pgrs_now(data); - if(proxy_ctx->connected) { - *done = TRUE; - goto out; - } - result = proxy_h3_progress_egress_ngtcp2(cf, data, &pktx); - /* we do not expect to be able to recv anything yet */ - goto out; - } - - result = proxy_h3_progress_ingress_ngtcp2(cf, data, &pktx); - if(result) - goto out; - - result = proxy_h3_progress_egress_ngtcp2(cf, data, &pktx); - if(result) - goto out; - - if(ngtcp2_conn_get_handshake_completed(proxy_ctx->ngtcp2_ctx->qconn)) { - result = proxy_ctx->ngtcp2_ctx->tls_vrfy_result; - if(!result) { - CURL_TRC_CF(data, cf, "peer verified"); - proxy_ctx->connected = TRUE; - *done = TRUE; - connkeep(cf->conn, "HTTP/3 default"); - } - } - -out: - if(proxy_ctx->ngtcp2_ctx->qconn && - ((result == CURLE_RECV_ERROR) || (result == CURLE_SEND_ERROR)) && - ngtcp2_conn_in_draining_period(proxy_ctx->ngtcp2_ctx->qconn)) { - const ngtcp2_ccerr *cerr = - ngtcp2_conn_get_ccerr(proxy_ctx->ngtcp2_ctx->qconn); - - result = CURLE_COULDNT_CONNECT; - if(cerr) { - CURL_TRC_CF(data, cf, "connect error, type=%d, code=%" PRIu64, - cerr->type, cerr->error_code); - switch(cerr->type) { - case NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION: - CURL_TRC_CF(data, cf, "error in version negotiation"); - break; - default: - if(cerr->error_code >= NGTCP2_CRYPTO_ERROR) { - CURL_TRC_CF(data, cf, "crypto error, tls alert=%u", - (unsigned int)(cerr->error_code & 0xffU)); - } - else if(cerr->error_code == NGTCP2_CONNECTION_REFUSED) { - CURL_TRC_CF(data, cf, "connection refused by server"); - /* When a QUIC server instance is shutting down, it may send us a - * CONNECTION_CLOSE with this code right away. We want - * to keep on trying in this case. */ - result = CURLE_WEIRD_SERVER_REPLY; - } - } - } - } - -#ifdef CURLVERBOSE - if(result) { - bool is_ipv6; - struct ip_quadruple ip; - if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ip)) - infof(data, "QUIC connect to %s port %u failed: %s", - ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); - } -#endif - if(!result && proxy_ctx->ngtcp2_ctx->qconn) { - result = check_and_set_expiry_ngtcp2(cf, data, &pktx); - } - if(result || *done) - CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); - CF_DATA_RESTORE(cf, save); - return result; -} - -static CURLcode H3_CONNECT(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h3_tunnel_stream *ts) +static CURLcode cf_h3_proxy_tunnel(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_tunnel_stream *ts, + bool *pdone) { struct cf_h3_proxy_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; DEBUGASSERT(ts); DEBUGASSERT(ts->authority); - + *pdone = FALSE; do { switch(ts->state) { case H3_TUNNEL_INIT: CURL_TRC_CF(data, cf, "[0] CONNECT start for %s", ts->authority); - result = h3_submit_CONNECT(cf, data, ts); + result = cf_h3_proxy_submit_CONNECT(cf, data, ts); if(result) goto out; - h3_tunnel_go_state(cf, ts, H3_TUNNEL_CONNECT, data, - (bool)ctx->udp_tunnel); + h3_tunnel_go_state(cf, ts, H3_TUNNEL_CONNECT, data); - result = proxy_h3_progress_egress_ngtcp2(cf, data, NULL); + result = Curl_cf_ngtcp2_progress_egress(cf, data, NULL); if(result) goto out; FALLTHROUGH(); @@ -3177,19 +1106,17 @@ static CURLcode H3_CONNECT(struct Curl_cfilter *cf, case H3_TUNNEL_CONNECT: /* Non-blocking: call ingress/egress once and return. * The multi interface will call us again when ready. */ - result = proxy_h3_progress_ingress_ngtcp2(cf, data, NULL); + result = Curl_cf_ngtcp2_progress_ingress(cf, data, NULL); if(result) goto out; - result = proxy_h3_progress_egress_ngtcp2(cf, data, NULL); + result = Curl_cf_ngtcp2_progress_egress(cf, data, NULL); if(result && result != CURLE_AGAIN) { - h3_tunnel_go_state(cf, ts, H3_TUNNEL_FAILED, data, - (bool)ctx->udp_tunnel); + h3_tunnel_go_state(cf, ts, H3_TUNNEL_FAILED, data); goto out; } if(ts->has_final_response) { - h3_tunnel_go_state(cf, ts, H3_TUNNEL_RESPONSE, data, - (bool)ctx->udp_tunnel); + h3_tunnel_go_state(cf, ts, H3_TUNNEL_RESPONSE, data); } else { /* Not done yet, return and let multi interface call us again */ @@ -3200,13 +1127,14 @@ static CURLcode H3_CONNECT(struct Curl_cfilter *cf, case H3_TUNNEL_RESPONSE: DEBUGASSERT(ts->has_final_response); - result = h3_proxy_inspect_response(cf, data, ts); + result = cf_h3_proxy_inspect_response(cf, data, ts); if(result) goto out; ctx->connected = TRUE; break; case H3_TUNNEL_ESTABLISHED: + *pdone = TRUE; return CURLE_OK; case H3_TUNNEL_FAILED: @@ -3220,49 +1148,30 @@ static CURLcode H3_CONNECT(struct Curl_cfilter *cf, out: if((result && (result != CURLE_AGAIN)) || ctx->tunnel.closed) - h3_tunnel_go_state(cf, ts, H3_TUNNEL_FAILED, data, (bool)ctx->udp_tunnel); + h3_tunnel_go_state(cf, ts, H3_TUNNEL_FAILED, data); return result; } static CURLcode cf_h3_proxy_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) { - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; + struct cf_h3_proxy_ctx *pctx = cf->ctx; struct cf_call_data save = { 0 }; CURLcode result = CURLE_OK; - timediff_t check; - struct h3_tunnel_stream *ts = &proxy_ctx->tunnel; + struct h3_tunnel_stream *ts = &pctx->tunnel; bool data_saved = FALSE; - /* Curl_cft_http_proxy --> Curl_cft_h3_proxy --> HAPPY-EYEBALLS --> UDP */ - if(cf->connected) { - *done = TRUE; - return CURLE_OK; - } - - *done = FALSE; - - check = Curl_timeleft_ms(data); - if(check <= 0) { - failf(data, "Proxy CONNECT aborted due to timeout"); - result = CURLE_OPERATION_TIMEDOUT; - goto out; - } - - result = cf_h3_proxy_quic_connect(cf, data, done); - if(*done != TRUE) + result = Curl_cf_ngtcp2_cmn_connect(cf, data, done); + if(result || !*done) goto out; CF_DATA_SAVE(save, cf, data); data_saved = TRUE; /* At this point the QUIC is connected, but the proxy isn't connected */ - *done = FALSE; - - result = H3_CONNECT(cf, data, ts); + result = cf_h3_proxy_tunnel(cf, data, ts, done); out: - *done = (result == CURLE_OK) && (ts->state == H3_TUNNEL_ESTABLISHED); if(*done) { cf->connected = TRUE; /* The real request will follow the CONNECT, reset request partially */ @@ -3275,100 +1184,14 @@ out: return result; } -static CURLcode h3_proxy_data_pause(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool pause) -{ - (void)cf; - if(!pause) { - /* unpaused. make it run again right away */ - Curl_multi_mark_dirty(data); - } - return CURLE_OK; -} - -static void h3_proxy_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct h3_proxy_stream_ctx *stream; - - if(!ctx) - return; - - stream = H3_PROXY_STREAM_CTX(ctx, data); - if(stream) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] easy handle is done", stream->id); - cf_ngtcp2_proxy_stream_close(cf, data, stream); - Curl_uint32_hash_remove(&ctx->streams, data->mid); - if(!Curl_uint32_hash_count(&ctx->streams)) - cf_ngtcp2_proxy_setup_keep_alive(cf, data); - } -} - -static CURLcode cf_h3_proxy_cntrl(struct Curl_cfilter *cf, - struct Curl_easy *data, - int event, int arg1, void *arg2) -{ - struct cf_h3_proxy_ctx *proxy_ctx = cf->ctx; - struct cf_call_data save; - CURLcode result = CURLE_OK; - - CF_DATA_SAVE(save, cf, data); - - (void)arg1; - (void)arg2; - switch(event) { - case CF_CTRL_DATA_SETUP: - break; - case CF_CTRL_DATA_PAUSE: - result = h3_proxy_data_pause(cf, data, (arg1 != 0)); - break; - case CF_CTRL_DATA_DONE: - h3_proxy_data_done(cf, data); - break; - case CF_CTRL_DATA_DONE_SEND: { - struct cf_ngtcp2_proxy_ctx *ctx = proxy_ctx->ngtcp2_ctx; - struct h3_proxy_stream_ctx *stream = NULL; - if(ctx) { - stream = H3_PROXY_STREAM_CTX(ctx, data); - if(stream && !stream->send_closed && - (H3_STREAM_ID(stream) != proxy_ctx->tunnel.stream_id)) { - stream->send_closed = TRUE; - stream->upload_left = Curl_bufq_len(&stream->sendbuf) - - stream->sendbuf_len_in_flight; - (void)nghttp3_conn_resume_stream(ctx->h3conn, H3_STREAM_ID(stream)); - } - } - break; - } - case CF_CTRL_CONN_INFO_UPDATE: - if(!cf->sockindex && cf->connected) { - cf->conn->httpversion_seen = 30; - Curl_conn_set_multiplex(cf->conn); - } - break; - default: - break; - } - - CF_DATA_RESTORE(cf, save); - return result; -} - static void cf_h3_proxy_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h3_proxy_ctx *ctx = cf->ctx; + (void)data; if(ctx) { - /* Clean up the ngtcp2 context properly */ - if(ctx->ngtcp2_ctx) { - CURL_TRC_CF(data, cf, "cf_ngtcp2_proxy_ctx_close()"); - cf_ngtcp2_proxy_close(cf, data); - cf_ngtcp2_proxy_ctx_free(ctx->ngtcp2_ctx); - ctx->ngtcp2_ctx = NULL; - } + CURL_TRC_CF(data, cf, "cf_h3_proxy_destroy()"); cf_h3_proxy_ctx_free(ctx); cf->ctx = NULL; } @@ -3377,7 +1200,7 @@ static void cf_h3_proxy_destroy(struct Curl_cfilter *cf, static CURLcode cf_h3_proxy_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) { - return cf_ngtcp2_proxy_shutdown(cf, data, done); + return Curl_cf_ngtcp2_cmn_shutdown(cf, data, done); } struct Curl_cftype Curl_cft_h3_proxy = { @@ -3391,24 +1214,29 @@ struct Curl_cftype Curl_cft_h3_proxy = { cf_h3_proxy_data_pending, cf_h3_proxy_send, cf_h3_proxy_recv, - cf_h3_proxy_cntrl, - cf_h3_proxy_is_alive, + Curl_cf_def_cntrl, + Curl_cf_ngtcp2_cmn_conn_is_alive, Curl_cf_def_conn_keep_alive, - cf_h3_proxy_query, + Curl_cf_def_query, }; CURLcode Curl_cf_ngtcp2_proxy_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out) + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { struct Curl_cfilter *cf = NULL; struct cf_h3_proxy_ctx *ctx; CURLcode result = CURLE_OUT_OF_MEMORY; - if((transport_out != TRNSPRT_QUIC) || (!conn->http_proxy.peer)) + if(!tunnel_peer) + return CURLE_FAILED_INIT; + if((transport_peer != TRNSPRT_QUIC) || (!conn->http_proxy.peer)) return CURLE_FAILED_INIT; ctx = curlx_calloc(1, sizeof(*ctx)); @@ -3416,15 +1244,18 @@ CURLcode Curl_cf_ngtcp2_proxy_create(struct Curl_cfilter **pcf, result = CURLE_OUT_OF_MEMORY; goto out; } - ctx->udp_tunnel = (transport_in == TRNSPRT_QUIC); + result = cf_h3_proxy_ctx_init(ctx, origin, peer, &conn->proxy_ssl_config, + tunnel_peer, tunnel_transport); + if(result) + goto out; result = Curl_cf_create(&cf, &Curl_cft_h3_proxy, ctx); if(result) goto out; cf->conn = conn; - result = Curl_cf_udp_create(&cf->next, data, conn, addr, - TRNSPRT_QUIC, TRNSPRT_QUIC); + result = Curl_cf_udp_create(&cf->next, data, origin, peer, TRNSPRT_QUIC, + conn, addr, NULL, TRNSPRT_QUIC); if(result) goto out; cf->next->conn = cf->conn; @@ -3439,14 +1270,16 @@ out: cf_h3_proxy_ctx_free(ctx); } else - CURL_TRC_CF(data, cf, "created, udp_tunnel=%d", ctx->udp_tunnel); + CURL_TRC_CF(data, cf, "created, udp_tunnel=%d", ctx->tunnel.udp); return result; } CURLcode Curl_cf_ngtcp2_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - struct Curl_peer *dest, - bool udp_tunnel) + struct Curl_peer *origin, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { struct Curl_cfilter *cf = NULL; struct cf_h3_proxy_ctx *ctx; @@ -3456,8 +1289,11 @@ CURLcode Curl_cf_ngtcp2_proxy_insert_after(struct Curl_cfilter *cf_at, ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) goto out; - Curl_peer_link(&ctx->dest, dest); - ctx->udp_tunnel = udp_tunnel; + result = cf_h3_proxy_ctx_init(ctx, origin, peer, + &cf_at->conn->proxy_ssl_config, + tunnel_peer, tunnel_transport); + if(result) + goto out; result = Curl_cf_create(&cf, &Curl_cft_h3_proxy, ctx); if(result) diff --git a/lib/vquic/cf-ngtcp2-proxy.h b/lib/vquic/cf-ngtcp2-proxy.h index fc176fbab4..acdee0e463 100644 --- a/lib/vquic/cf-ngtcp2-proxy.h +++ b/lib/vquic/cf-ngtcp2-proxy.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_H3_PROXY_H -#define HEADER_CURL_H3_PROXY_H +#ifndef HEADER_CURL_VQUIC_CF_NGTCP2_PROXY_H +#define HEADER_CURL_VQUIC_CF_NGTCP2_PROXY_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -32,16 +32,21 @@ CURLcode Curl_cf_ngtcp2_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - struct Curl_peer *dest, - bool udp_tunnel); + struct Curl_peer *origin, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); CURLcode Curl_cf_ngtcp2_proxy_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); #endif -#endif /* HEADER_CURL_H3_PROXY_H */ +#endif /* HEADER_CURL_VQUIC_CF_NGTCP2_PROXY_H */ diff --git a/lib/vquic/cf-ngtcp2.c b/lib/vquic/cf-ngtcp2.c index 3d1c8a15f8..0a3679b7b6 100644 --- a/lib/vquic/cf-ngtcp2.c +++ b/lib/vquic/cf-ngtcp2.c @@ -25,28 +25,6 @@ #if !defined(CURL_DISABLE_HTTP) && defined(USE_NGTCP2) && defined(USE_NGHTTP3) -#include - -#ifdef USE_OPENSSL -#include -#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL) -#include -#elif defined(OPENSSL_QUIC_API2) -#include -#else -#include -#endif -#include "vtls/openssl.h" -#elif defined(USE_GNUTLS) -#include -#include "vtls/gtls.h" -#elif defined(USE_WOLFSSL) -#include -#include "vtls/wolfssl.h" -#endif - -#include - #include "urldata.h" #include "url.h" #include "uint-hash.h" @@ -66,918 +44,14 @@ #include "bufref.h" #include "vquic/vquic.h" #include "vquic/vquic_int.h" -#include "vquic/vquic-tls.h" -#include "vtls/vtls.h" -#include "vtls/vtls_scache.h" +#include "vquic/cf-ngtcp2-cmn.h" #include "vquic/cf-ngtcp2.h" -#define QUIC_MAX_STREAMS (256 * 1024) -#define QUIC_HANDSHAKE_TIMEOUT (10 * NGTCP2_SECONDS) -#define QUIC_TUNNEL_INBUF_SIZE (64 * 1024) - -/* We announce a small window size in transport param to the server, - * and grow that immediately to max when no rate limit is in place. - * We need to start small as we are not able to decrease it. */ -#define H3_STREAM_WINDOW_SIZE_INITIAL (32 * 1024) -#define H3_STREAM_WINDOW_SIZE_MAX (10 * 1024 * 1024) -#define H3_CONN_WINDOW_SIZE_MAX (100 * H3_STREAM_WINDOW_SIZE_MAX) - -#define H3_STREAM_CHUNK_SIZE (64 * 1024) -#if H3_STREAM_CHUNK_SIZE < NGTCP2_MAX_UDP_PAYLOAD_SIZE -#error H3_STREAM_CHUNK_SIZE smaller than NGTCP2_MAX_UDP_PAYLOAD_SIZE -#endif - -/* The pool keeps spares around and half of a full stream window - * seems good. More does not seem to improve performance. - * The benefit of the pool is that stream buffers do not keep - * spares. Memory consumption goes down when streams run empty, - * have a large upload done, etc. */ -#define H3_STREAM_POOL_SPARES 2 -/* The max amount of un-acked upload data we keep around per stream */ -#define H3_STREAM_SEND_BUFFER_MAX (10 * 1024 * 1024) -#define H3_STREAM_SEND_CHUNKS \ - (H3_STREAM_SEND_BUFFER_MAX / H3_STREAM_CHUNK_SIZE) -#define QUIC_TUNNEL_INGRESS_PKT_LIMIT 1000 - -/* - * Store ngtcp2 version info in this buffer. - */ -void Curl_ngtcp2_ver(char *p, size_t len) -{ - const ngtcp2_info *ng2 = ngtcp2_version(0); - const nghttp3_info *ht3 = nghttp3_version(0); - (void)curl_msnprintf(p, len, "ngtcp2/%s nghttp3/%s", - ng2->version_str, ht3->version_str); -} - -struct cf_ngtcp2_ctx { - struct cf_quic_ctx q; - struct ssl_peer peer; - struct curl_tls_ctx tls; -#ifdef OPENSSL_QUIC_API2 - ngtcp2_crypto_ossl_ctx *ossl_ctx; -#endif - ngtcp2_path connected_path; - ngtcp2_conn *qconn; - ngtcp2_cid dcid; - ngtcp2_cid scid; - uint32_t version; - ngtcp2_settings settings; - ngtcp2_transport_params transport_params; - ngtcp2_ccerr last_error; - ngtcp2_crypto_conn_ref conn_ref; - struct cf_call_data call_data; - nghttp3_conn *h3conn; - nghttp3_settings h3settings; - struct curltime started_at; /* time the current attempt started */ - struct curltime handshake_at; /* time connect handshake finished */ - struct bufc_pool stream_bufcp; /* chunk pool for streams */ - struct dynbuf scratch; /* temp buffer for header construction */ - struct uint_hash streams; /* hash data->mid to h3_stream_ctx */ - uint64_t used_bidi_streams; /* bidi streams we have opened */ - uint64_t max_bidi_streams; /* max bidi streams we can open */ - size_t earlydata_max; /* max amount of early data supported by - server on session reuse */ - size_t earlydata_skip; /* sending bytes to skip when earlydata - is accepted by peer */ - CURLcode tls_vrfy_result; /* result of TLS peer verification */ - int qlogfd; - unsigned char *tunnel_inbuf; /* ingress buffer for tunneled packets */ - size_t tunnel_inbuf_len; - BIT(initialized); - BIT(tls_handshake_complete); /* TLS handshake is done */ - BIT(use_earlydata); /* Using 0RTT data */ - BIT(earlydata_accepted); /* 0RTT was accepted by server */ - BIT(shutdown_started); /* queued shutdown packets */ -}; - -/* How to access `call_data` from a cf_ngtcp2 filter */ -#undef CF_CTX_CALL_DATA -#define CF_CTX_CALL_DATA(cf) ((struct cf_ngtcp2_ctx *)(cf)->ctx)->call_data - -static void h3_stream_hash_free(unsigned int id, void *stream); - -static void cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx) -{ - DEBUGASSERT(!ctx->initialized); - ctx->qlogfd = -1; - ctx->tunnel_inbuf = NULL; - ctx->tunnel_inbuf_len = 0; - ctx->version = NGTCP2_PROTO_VER_MAX; - Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, - H3_STREAM_POOL_SPARES); - curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); - Curl_uint32_hash_init(&ctx->streams, 63, h3_stream_hash_free); - ctx->initialized = TRUE; -} - -static void cf_ngtcp2_ctx_free(struct cf_ngtcp2_ctx *ctx) -{ - if(ctx && ctx->initialized) { - Curl_vquic_tls_cleanup(&ctx->tls); - vquic_ctx_free(&ctx->q); - Curl_bufcp_free(&ctx->stream_bufcp); - curlx_dyn_free(&ctx->scratch); - Curl_uint32_hash_destroy(&ctx->streams); - Curl_ssl_peer_cleanup(&ctx->peer); - curlx_safefree(ctx->tunnel_inbuf); - ctx->tunnel_inbuf_len = 0; - } - curlx_free(ctx); -} - -static void cf_ngtcp2_setup_keep_alive(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const ngtcp2_transport_params *rp; - /* Peer should have sent us its transport parameters. If it - * announces a positive `max_idle_timeout` it closes the - * connection when it does not hear from us for that time. - * - * Some servers use this as a keep-alive timer at a rather low - * value. We are doing HTTP/3 here and waiting for the response - * to a request may take a considerable amount of time. We need - * to prevent the peer's QUIC stack from closing in this case. - */ - if(!ctx->qconn) - return; - - rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); - if(!rp || !rp->max_idle_timeout) { - ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); - CURL_TRC_CF(data, cf, "no peer idle timeout, unset keep-alive"); - } - else if(!Curl_uint32_hash_count(&ctx->streams)) { - ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); - CURL_TRC_CF(data, cf, "no active streams, unset keep-alive"); - } - else { - ngtcp2_duration keep_ns; - keep_ns = (rp->max_idle_timeout > 1) ? (rp->max_idle_timeout / 2) : 1; - ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, keep_ns); - CURL_TRC_CF(data, cf, "peer idle timeout is %" PRIu64 "ms, " - "set keep-alive to %" PRIu64 " ms.", - (rp->max_idle_timeout / NGTCP2_MILLISECONDS), - (keep_ns / NGTCP2_MILLISECONDS)); - } -} - -struct pkt_io_ctx; -static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx); -static CURLcode cf_progress_egress(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx); - -/** - * All about the H3 internals of a stream - */ -struct h3_stream_ctx { - int64_t id; /* HTTP/3 protocol identifier */ - struct bufq sendbuf; /* h3 request body */ - struct h1_req_parser h1; /* h1 request parsing */ - size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ - uint64_t error3; /* HTTP/3 stream error code */ - curl_off_t upload_left; /* number of request bytes left to upload */ - uint64_t rx_offset; /* current receive offset */ - uint64_t rx_offset_max; /* allowed receive offset */ - uint64_t window_size_max; /* max flow control window set for stream */ - int status_code; /* HTTP status code */ - CURLcode xfer_result; /* result from xfer_resp_write(_hd) */ - BIT(resp_hds_complete); /* we have a complete, final response */ - BIT(closed); /* TRUE on stream close */ - BIT(reset); /* TRUE on stream reset */ - BIT(send_closed); /* stream is local closed */ - BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ -}; - -static void h3_stream_ctx_free(struct h3_stream_ctx *stream) -{ - Curl_bufq_free(&stream->sendbuf); - Curl_h1_req_parse_free(&stream->h1); - curlx_free(stream); -} - -static void h3_stream_hash_free(unsigned int id, void *stream) -{ - (void)id; - DEBUGASSERT(stream); - h3_stream_ctx_free((struct h3_stream_ctx *)stream); -} - -static CURLcode h3_data_setup(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - - if(!data) - return CURLE_FAILED_INIT; - - if(stream) - return CURLE_OK; - - stream = curlx_calloc(1, sizeof(*stream)); - if(!stream) - return CURLE_OUT_OF_MEMORY; - - stream->id = -1; - stream->rx_offset = 0; - stream->rx_offset_max = H3_STREAM_WINDOW_SIZE_INITIAL; - - /* on send, we control how much we put into the buffer */ - Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, - H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); - stream->sendbuf_len_in_flight = 0; - stream->window_size_max = H3_STREAM_WINDOW_SIZE_INITIAL; - Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - - if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { - h3_stream_ctx_free(stream); - return CURLE_OUT_OF_MEMORY; - } - - if(Curl_uint32_hash_count(&ctx->streams) == 1) - cf_ngtcp2_setup_keep_alive(cf, data); - - return CURLE_OK; -} - -#if NGTCP2_VERSION_NUM < 0x011100 -struct cf_ngtcp2_sfind_ctx { - int64_t stream_id; - struct h3_stream_ctx *stream; - uint32_t mid; -}; - -static bool cf_ngtcp2_sfind(uint32_t mid, void *value, void *user_data) -{ - struct cf_ngtcp2_sfind_ctx *fctx = user_data; - struct h3_stream_ctx *stream = value; - - if(fctx->stream_id == stream->id) { - fctx->mid = mid; - fctx->stream = stream; - return FALSE; - } - return TRUE; /* continue */ -} - -static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, - int64_t stream_id) -{ - struct cf_ngtcp2_sfind_ctx fctx; - fctx.stream_id = stream_id; - fctx.stream = NULL; - Curl_uint32_hash_visit(&ctx->streams, cf_ngtcp2_sfind, &fctx); - return fctx.stream; -} -#else -static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, - int64_t stream_id) -{ - struct Curl_easy *data = - ngtcp2_conn_get_stream_user_data(ctx->qconn, stream_id); - - if(!data) { - return NULL; - } - - return H3_STREAM_CTX(ctx, data); -} -#endif - -static void cf_ngtcp2_stream_close(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h3_stream_ctx *stream) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - DEBUGASSERT(data); - DEBUGASSERT(stream); - if(!stream->closed && ctx->qconn && ctx->h3conn) { - CURLcode result; - - nghttp3_conn_set_stream_user_data(ctx->h3conn, stream->id, NULL); - ngtcp2_conn_set_stream_user_data(ctx->qconn, stream->id, NULL); - stream->closed = TRUE; - (void)ngtcp2_conn_shutdown_stream(ctx->qconn, 0, stream->id, - NGHTTP3_H3_REQUEST_CANCELLED); - result = cf_progress_egress(cf, data, NULL); - if(result) - CURL_TRC_CF(data, cf, "[%" PRId64 "] cancel stream -> %d", - stream->id, result); - } -} - -static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - (void)cf; - if(stream) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] easy handle is done", stream->id); - cf_ngtcp2_stream_close(cf, data, stream); - Curl_uint32_hash_remove(&ctx->streams, data->mid); - if(!Curl_uint32_hash_count(&ctx->streams)) - cf_ngtcp2_setup_keep_alive(cf, data); - } -} - -struct pkt_io_ctx { - struct Curl_cfilter *cf; - struct Curl_easy *data; - ngtcp2_tstamp ts; - ngtcp2_path_storage ps; -}; - -static void pktx_update_time(struct Curl_easy *data, - struct pkt_io_ctx *pktx, - struct Curl_cfilter *cf) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const struct curltime *pnow = Curl_pgrs_now(data); - - vquic_ctx_update_time(&ctx->q, pnow); - pktx->ts = ((ngtcp2_tstamp)pnow->tv_sec * NGTCP2_SECONDS) + - ((ngtcp2_tstamp)pnow->tv_usec * NGTCP2_MICROSECONDS); -} - -static void pktx_init(struct pkt_io_ctx *pktx, - struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const struct curltime *pnow = Curl_pgrs_now(data); - - pktx->cf = cf; - pktx->data = data; - ngtcp2_path_storage_zero(&pktx->ps); - vquic_ctx_set_time(&ctx->q, pnow); - pktx->ts = ((ngtcp2_tstamp)pnow->tv_sec * NGTCP2_SECONDS) + - ((ngtcp2_tstamp)pnow->tv_usec * NGTCP2_MICROSECONDS); -} - static int cb_h3_acked_req_body(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 Curl_cfilter *cf = conn_ref->user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - return ctx->qconn; -} - -#ifdef DEBUG_NGTCP2 -static void quic_printf(void *user_data, const char *fmt, ...) -{ - va_list ap; - (void)user_data; - va_start(ap, fmt); - curl_mvfprintf(stderr, fmt, ap); - va_end(ap); - curl_mfprintf(stderr, "\n"); -} -#endif - -static void qlog_callback(void *user_data, uint32_t flags, - const void *data, size_t datalen) -{ - struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - (void)flags; - if(ctx->qlogfd != -1) { - ssize_t rc = write(ctx->qlogfd, data, datalen); - if(rc == -1) { - /* on write error, stop further write attempts */ - curlx_close(ctx->qlogfd); - ctx->qlogfd = -1; - } - } -} - -static void quic_settings(struct cf_ngtcp2_ctx *ctx, - struct Curl_easy *data, - struct pkt_io_ctx *pktx) -{ - ngtcp2_settings *s = &ctx->settings; - ngtcp2_transport_params *t = &ctx->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 = pktx->ts; - s->handshake_timeout = (data->set.connecttimeout > 0) ? - data->set.connecttimeout * NGTCP2_MILLISECONDS : QUIC_HANDSHAKE_TIMEOUT; - s->max_window = H3_CONN_WINDOW_SIZE_MAX; - s->max_stream_window = 0; /* disable ngtcp2 auto-tuning of window */ - s->no_pmtud = FALSE; -#ifdef NGTCP2_SETTINGS_V3 - /* try ten times the ngtcp2 defaults here for problems with Caddy */ - s->glitch_ratelim_burst = 1000 * 10; - s->glitch_ratelim_rate = 33 * 10; -#endif - t->initial_max_data = s->max_window; - t->initial_max_stream_data_bidi_local = H3_STREAM_WINDOW_SIZE_INITIAL; - t->initial_max_stream_data_bidi_remote = H3_STREAM_WINDOW_SIZE_INITIAL; - t->initial_max_stream_data_uni = t->initial_max_data; - t->initial_max_streams_bidi = QUIC_MAX_STREAMS; - t->initial_max_streams_uni = QUIC_MAX_STREAMS; - t->max_idle_timeout = 0; /* no idle timeout from our side */ - if(ctx->qlogfd != -1) { - s->qlog_write = qlog_callback; - } -} - -static CURLcode init_ngh3_conn(struct Curl_cfilter *cf, - struct Curl_easy *data); - -static int cb_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; - struct Curl_easy *data; - - (void)tconn; - DEBUGASSERT(ctx); - data = CF_DATA_CURRENT(cf); - DEBUGASSERT(data); - if(!ctx || !data) - return NGTCP2_ERR_CALLBACK_FAILURE; - - ctx->handshake_at = *Curl_pgrs_now(data); - ctx->tls_handshake_complete = TRUE; - Curl_vquic_report_handshake(&ctx->tls, cf, data); - - ctx->tls_vrfy_result = Curl_vquic_tls_verify_peer(&ctx->tls, cf, - data, &ctx->peer); - if(ctx->tls_vrfy_result) - return NGTCP2_ERR_CALLBACK_FAILURE; - -#ifdef CURLVERBOSE - if(Curl_trc_is_verbose(data)) { - const ngtcp2_transport_params *rp; - rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); - CURL_TRC_CF(data, cf, "handshake complete after %" FMT_TIMEDIFF_T - "ms, remote transport[max_udp_payload=%" PRIu64 - ", initial_max_data=%" PRIu64 "]", - curlx_ptimediff_ms(&ctx->handshake_at, &ctx->started_at), - rp->max_udp_payload_size, rp->initial_max_data); - } -#endif - - /* In case of earlydata, where we simulate being connected, update - * the handshake time when we really did connect */ - if(ctx->use_earlydata) - Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); - if(ctx->use_earlydata) { -#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) - ctx->earlydata_accepted = - (SSL_get_early_data_status(ctx->tls.ossl.ssl) != - SSL_EARLY_DATA_REJECTED); -#endif -#ifdef USE_GNUTLS - int flags = gnutls_session_get_flags(ctx->tls.gtls.session); - ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA); -#endif -#ifdef USE_WOLFSSL -#ifdef WOLFSSL_EARLY_DATA - ctx->earlydata_accepted = - (wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) != - WOLFSSL_EARLY_DATA_REJECTED); -#else - DEBUGASSERT(0); /* should not come here if ED is disabled. */ - ctx->earlydata_accepted = FALSE; -#endif /* WOLFSSL_EARLY_DATA */ -#endif - CURL_TRC_CF(data, cf, "server did%s accept %zu bytes of early data", - ctx->earlydata_accepted ? "" : " not", ctx->earlydata_skip); - Curl_pgrsEarlyData(data, ctx->earlydata_accepted ? - (curl_off_t)ctx->earlydata_skip : - -(curl_off_t)ctx->earlydata_skip); - } - return 0; -} - -static void cf_ngtcp2_conn_close(struct Curl_cfilter *cf, - struct Curl_easy *data); - -static bool cf_ngtcp2_err_is_fatal(int code) -{ - return (NGTCP2_ERR_FATAL >= code) || - (NGTCP2_ERR_DROP_CONN == code) || - (NGTCP2_ERR_IDLE_CLOSE == code); -} - -static void cf_ngtcp2_err_set(struct Curl_cfilter *cf, - struct Curl_easy *data, int code) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - if(!ctx->last_error.error_code) { - if(NGTCP2_ERR_CRYPTO == code) { - ngtcp2_ccerr_set_tls_alert(&ctx->last_error, - ngtcp2_conn_get_tls_alert(ctx->qconn), - NULL, 0); - } - else { - ngtcp2_ccerr_set_liberr(&ctx->last_error, code, NULL, 0); - } - } - if(cf_ngtcp2_err_is_fatal(code)) - cf_ngtcp2_conn_close(cf, data); -} - -static bool cf_ngtcp2_h3_err_is_fatal(int code) -{ - return (NGHTTP3_ERR_FATAL >= code) || - (NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM == code); -} - -static void cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, - struct Curl_easy *data, int code) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - if(!ctx->last_error.error_code) { - ngtcp2_ccerr_set_application_error(&ctx->last_error, - nghttp3_err_infer_quic_app_error_code(code), NULL, 0); - } - if(cf_ngtcp2_h3_err_is_fatal(code)) - cf_ngtcp2_conn_close(cf, data); -} - -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 Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - nghttp3_ssize rc; - uint64_t nconsumed; - int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - (void)offset; - - rc = nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin); - if(rc < 0) { - if(data && stream) { - CURL_TRC_CF(data, cf, "[%" PRId64 "] error on known stream, " - "reset=%d, closed=%d", - stream_id, stream->reset, stream->closed); - } - return NGTCP2_ERR_CALLBACK_FAILURE; - } - nconsumed = (uint64_t)rc; - if(nconsumed) { - /* 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); - if(stream) { - stream->rx_offset += nconsumed; - stream->rx_offset_max += 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 Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - int rv; - (void)stream_id; - (void)tconn; - (void)offset; - (void)datalen; - (void)stream_user_data; - - rv = nghttp3_conn_add_ack_offset(ctx->h3conn, stream_id, datalen); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - 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 Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - int rv; - - (void)tconn; - /* stream is closed... */ - if(!data) - data = CF_DATA_CURRENT(cf); - if(!data) - return NGTCP2_ERR_CALLBACK_FAILURE; - - if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { - app_error_code = NGHTTP3_H3_NO_ERROR; - } - - rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id, app_error_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] quic close(app_error=%" - PRIu64 ") -> %d", stream_id, app_error_code, rv); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - cf_ngtcp2_h3_err_set(cf, data, rv); - 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 Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - int rv; - (void)tconn; - (void)final_size; - (void)app_error_code; - - rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - 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 Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - int rv; - (void)tconn; - (void)app_error_code; - (void)stream_user_data; - - rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - 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) -{ - struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = CF_DATA_CURRENT(cf); - - (void)tconn; - ctx->max_bidi_streams = max_streams; - if(data) - CURL_TRC_CF(data, cf, "max bidi streams now %" PRIu64 ", used %" PRIu64, - ctx->max_bidi_streams, ctx->used_bidi_streams); - 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 Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *s_data = stream_user_data; - struct h3_stream_ctx *stream; - int rv; - (void)tconn; - (void)max_data; - - rv = nghttp3_conn_unblock_stream(ctx->h3conn, stream_id); - if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - stream = H3_STREAM_CTX(ctx, s_data); - if(stream && stream->quic_flow_blocked) { - CURL_TRC_CF(s_data, cf, "[%" PRId64 "] unblock quic flow", stream_id); - stream->quic_flow_blocked = FALSE; - Curl_multi_mark_dirty(s_data); - } - 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, fill 0 and call it *random*. */ - memset(dest, 0, destlen); - } -} - -/* for ngtcp2 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; -} - -#ifdef NGTCP2_CALLBACKS_V3 /* ngtcp2 v1.22.0+ */ -static int cb_get_new_connection_id2( - ngtcp2_conn *tconn, ngtcp2_cid *cid, - struct ngtcp2_stateless_reset_token *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->data, sizeof(token->data)); - if(result) - return NGTCP2_ERR_CALLBACK_FAILURE; - - return 0; -} -#endif - -static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_encryption_level level, - void *user_data) -{ - struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; - struct Curl_easy *data = CF_DATA_CURRENT(cf); - (void)tconn; - - if(level != NGTCP2_ENCRYPTION_LEVEL_1RTT) - return 0; - - DEBUGASSERT(ctx); - DEBUGASSERT(data); - if(ctx && data && !ctx->h3conn) { - if(init_ngh3_conn(cf, data)) - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} - -#if defined(_MSC_VER) && defined(_DLL) -#pragma warning(push) -#pragma warning(disable:4232) /* MSVC extension, dllimport identity */ -#endif - -static ngtcp2_callbacks ng_callbacks = { - ngtcp2_crypto_client_initial_cb, - NULL, /* recv_client_initial */ - ngtcp2_crypto_recv_crypto_data_cb, - cb_ngtcp2_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, /* for ngtcp2 ctx; - struct pkt_io_ctx local_pktx; - ngtcp2_tstamp expiry; - - if(!pktx) { - pktx_init(&local_pktx, cf, data); - pktx = &local_pktx; - } - else { - pktx_update_time(data, pktx, cf); - } - - expiry = ngtcp2_conn_get_expiry(ctx->qconn); - if(expiry != UINT64_MAX) { - if(expiry <= pktx->ts) { - CURLcode result; - int rv = ngtcp2_conn_handle_expiry(ctx->qconn, pktx->ts); - if(rv) { - failf(data, "ngtcp2_conn_handle_expiry returned error: %s", - ngtcp2_strerror(rv)); - cf_ngtcp2_err_set(cf, data, rv); - return CURLE_SEND_ERROR; - } - result = cf_progress_ingress(cf, data, pktx); - if(result) - return result; - result = cf_progress_egress(cf, data, pktx); - if(result) - return result; - /* ask again, things might have changed */ - expiry = ngtcp2_conn_get_expiry(ctx->qconn); - } - - if(expiry > pktx->ts) { - ngtcp2_duration timeout = expiry - pktx->ts; - if(timeout % NGTCP2_MILLISECONDS) { - timeout += NGTCP2_MILLISECONDS; - } - Curl_expire(data, (timediff_t)(timeout / NGTCP2_MILLISECONDS), - EXPIRE_QUIC); - } - } - return CURLE_OK; -} - static CURLcode cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, struct easy_pollset *ps) @@ -985,16 +59,13 @@ static CURLcode cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, struct cf_ngtcp2_ctx *ctx = cf->ctx; bool want_recv, want_send; CURLcode result = CURLE_OK; + curl_socket_t sock = (ctx->q.sockfd != CURL_SOCKET_BAD) ? + ctx->q.sockfd : Curl_conn_cf_get_socket(cf, data); - if(!ctx->qconn) + if(!ctx->qconn || (sock == CURL_SOCKET_BAD)) return CURLE_OK; - if(ctx->q.sockfd == CURL_SOCKET_BAD) { - /* Tunneled QUIC, no direct socket - delegate to next filter */ - return cf->next->cft->adjust_pollset(cf->next, data, ps); - } - - Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + Curl_pollset_check(data, ps, sock, &want_recv, &want_send); if(!want_send && !Curl_bufq_is_empty(&ctx->q.sendbuf)) want_send = TRUE; @@ -1012,7 +83,7 @@ static CURLcode cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, want_send = (!s_exhaust && want_send) || !Curl_bufq_is_empty(&ctx->q.sendbuf); - result = Curl_pollset_set(data, ps, ctx->q.sockfd, want_recv, want_send); + result = Curl_pollset_set(data, ps, sock, want_recv, want_send); CF_DATA_RESTORE(cf, save); } return result; @@ -1341,10 +412,9 @@ static nghttp3_callbacks ngh3_callbacks = { }; static CURLcode init_ngh3_conn(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + struct cf_ngtcp2_ctx *ctx) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id; int rc; if(ngtcp2_conn_get_streams_uni_left(ctx->qconn) < 3) { @@ -1364,42 +434,7 @@ static CURLcode init_ngh3_conn(struct Curl_cfilter *cf, return CURLE_OUT_OF_MEMORY; } - rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL); - if(rc) { - failf(data, "error creating HTTP/3 control stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = nghttp3_conn_bind_control_stream(ctx->h3conn, ctrl_stream_id); - if(rc) { - failf(data, "error binding HTTP/3 control stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_enc_stream_id, NULL); - if(rc) { - failf(data, "error creating HTTP/3 qpack encoding stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_dec_stream_id, NULL); - if(rc) { - failf(data, "error creating HTTP/3 qpack decoding stream: %s", - ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = nghttp3_conn_bind_qpack_streams(ctx->h3conn, qpack_enc_stream_id, - qpack_dec_stream_id); - if(rc) { - failf(data, "error binding HTTP/3 qpack streams: %s", ngtcp2_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - return CURLE_OK; + return Curl_cf_ngtcp2_h3_init_ctrls(ctx, data); } static CURLcode recv_closed_stream(struct Curl_cfilter *cf, @@ -1446,7 +481,7 @@ static CURLcode cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_ngtcp2_ctx *ctx = cf->ctx; struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; - struct pkt_io_ctx pktx; + struct cf_ngtcp2_io_ctx pktx; CURLcode result = CURLE_OK; int i; @@ -1467,7 +502,7 @@ static CURLcode cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, goto denied; } - pktx_init(&pktx, cf, data); + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); if(!stream || ctx->shutdown_started) { result = CURLE_RECV_ERROR; @@ -1484,7 +519,7 @@ static CURLcode cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, for(i = 0; i < 2; ++i) { if(stream->xfer_result) { CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id); - cf_ngtcp2_stream_close(cf, data, stream); + Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); result = stream->xfer_result; goto out; } @@ -1493,7 +528,7 @@ static CURLcode cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, goto out; } - if(!i && cf_progress_ingress(cf, data, &pktx)) { + if(!i && Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx)) { result = CURLE_RECV_ERROR; goto out; } @@ -1502,8 +537,10 @@ static CURLcode cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, result = CURLE_AGAIN; out: - result = Curl_1st_fatal(result, cf_progress_egress(cf, data, &pktx)); - result = Curl_1st_fatal(result, check_and_set_expiry(cf, data, &pktx)); + result = Curl_1st_fatal(result, + Curl_cf_ngtcp2_progress_egress(cf, data, &pktx)); + result = Curl_1st_fatal(result, + Curl_cf_ngtcp2_cmn_set_expiry(cf, data, &pktx)); if(ctx->tls_vrfy_result) result = ctx->tls_vrfy_result; denied: @@ -1630,7 +667,7 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, *pnwritten = 0; Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); - result = h3_data_setup(cf, data); + result = Curl_cf_ngtcp2_h3_stream_setup(cf, data); if(result) goto out; stream = H3_STREAM_CTX(ctx, data); @@ -1721,7 +758,7 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, "%d (%s)", stream->id, rc, nghttp3_strerror(rc)); break; } - cf_ngtcp2_stream_close(cf, data, stream); + Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); result = CURLE_SEND_ERROR; goto out; } @@ -1751,14 +788,14 @@ static CURLcode cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_ngtcp2_ctx *ctx = cf->ctx; struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); struct cf_call_data save; - struct pkt_io_ctx pktx; + struct cf_ngtcp2_io_ctx pktx; CURLcode result = CURLE_OK; CF_DATA_SAVE(save, cf, data); DEBUGASSERT(cf->connected); DEBUGASSERT(ctx->qconn); DEBUGASSERT(ctx->h3conn); - pktx_init(&pktx, cf, data); + Curl_cf_ngtcp2_io_ctx_init(&pktx, cf, data); *pnwritten = 0; /* handshake verification failed in callback, do not send anything */ @@ -1768,7 +805,7 @@ static CURLcode cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, } (void)eos; /* use for stream EOF and block handling */ - result = cf_progress_ingress(cf, data, &pktx); + result = Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx); if(result) goto out; @@ -1787,7 +824,7 @@ static CURLcode cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, } else if(stream->xfer_result) { CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id); - cf_ngtcp2_stream_close(cf, data, stream); + Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); result = stream->xfer_result; goto out; } @@ -1828,10 +865,11 @@ static CURLcode cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, ctx->earlydata_skip += *pnwritten; DEBUGASSERT(!result); - result = cf_progress_egress(cf, data, &pktx); + result = Curl_cf_ngtcp2_progress_egress(cf, data, &pktx); out: - result = Curl_1st_fatal(result, check_and_set_expiry(cf, data, &pktx)); + result = Curl_1st_fatal(result, + Curl_cf_ngtcp2_cmn_set_expiry(cf, data, &pktx)); if(ctx->tls_vrfy_result) result = ctx->tls_vrfy_result; denied: @@ -1841,361 +879,6 @@ denied: return result; } -struct cf_ngtcp2_recv_ctx { - struct pkt_io_ctx *pktx; - size_t pkt_count; -}; - -static CURLcode cf_ngtcp2_recv_pkts(const unsigned char *buf, size_t buflen, - size_t gso_size, - struct sockaddr_storage *remote_addr, - socklen_t remote_addrlen, int ecn, - void *userp) -{ - struct cf_ngtcp2_recv_ctx *rctx = userp; - struct pkt_io_ctx *pktx = rctx->pktx; - struct cf_ngtcp2_ctx *ctx = pktx->cf->ctx; - ngtcp2_pkt_info pi; - ngtcp2_path path; - size_t offset, pktlen; - int rv; - - if(!rctx->pkt_count) { - pktx_update_time(pktx->data, pktx, pktx->cf); - ngtcp2_path_storage_zero(&pktx->ps); - } - - if(ecn) - CURL_TRC_CF(pktx->data, pktx->cf, "vquic_recv(len=%zu, gso=%zu, ecn=%x)", - buflen, gso_size, ecn); - ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr, - ctx->q.local_addrlen); - ngtcp2_addr_init(&path.remote, (struct sockaddr *)remote_addr, - remote_addrlen); - pi.ecn = (uint8_t)ecn; - - for(offset = 0; offset < buflen; offset += gso_size) { - rctx->pkt_count++; - pktlen = ((offset + gso_size) <= buflen) ? gso_size : (buflen - offset); - rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, - buf + offset, pktlen, pktx->ts); - if(rv) { - CURL_TRC_CF(pktx->data, pktx->cf, "ingress, read_pkt -> %s (%d)", - ngtcp2_strerror(rv), rv); - cf_ngtcp2_err_set(pktx->cf, pktx->data, rv); - - 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 cf_progress_ingress(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct pkt_io_ctx local_pktx; - struct cf_ngtcp2_recv_ctx rctx; - CURLcode result = CURLE_OK; - - if(!pktx) { - pktx_init(&local_pktx, cf, data); - pktx = &local_pktx; - } - - result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); - if(result) - return result; - - rctx.pktx = pktx; - rctx.pkt_count = 0; - - if(ctx->q.sockfd != CURL_SOCKET_BAD) { - /* Direct UDP socket (via happy eyeballs) */ - return vquic_recv_packets(cf, data, &ctx->q, 1000, - cf_ngtcp2_recv_pkts, &rctx); - } - else { - /* Tunneled QUIC (CONNECT-UDP through proxy) */ - unsigned char *buf; - size_t max_udp_payload = QUIC_TUNNEL_INBUF_SIZE; - size_t pkt_limit = QUIC_TUNNEL_INGRESS_PKT_LIMIT; - size_t nread; - struct sockaddr_storage remote_addr; - socklen_t remote_addrlen; - - if(ctx->qconn) { - size_t max_path_payload; - max_path_payload = - ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn); - if(max_path_payload > max_udp_payload) - max_udp_payload = max_path_payload; - } - - if(ctx->tunnel_inbuf_len < max_udp_payload) { - unsigned char *newbuf = curlx_realloc(ctx->tunnel_inbuf, - max_udp_payload); - if(!newbuf) - return CURLE_OUT_OF_MEMORY; - ctx->tunnel_inbuf = newbuf; - ctx->tunnel_inbuf_len = max_udp_payload; - } - buf = ctx->tunnel_inbuf; - - while(pkt_limit--) { - result = Curl_conn_cf_recv(cf->next, data, (char *)buf, - ctx->tunnel_inbuf_len, &nread); - if(result == CURLE_AGAIN) { - /* no more data available at the moment */ - return CURLE_OK; - } - if(result) { - CURL_TRC_CF(data, cf, "ingress, recv from tunnel failed: %d", result); - return result; - } - if(nread == 0) { - /* tunnel closed */ - return CURLE_OK; - } - - memcpy(&remote_addr, ctx->connected_path.remote.addr, - ctx->connected_path.remote.addrlen); - remote_addrlen = (socklen_t)ctx->connected_path.remote.addrlen; - result = cf_ngtcp2_recv_pkts(buf, nread, nread, &remote_addr, - remote_addrlen, 0, &rctx); - if(result) - return result; - - if(!ctx->q.got_first_byte) { - ctx->q.got_first_byte = TRUE; - ctx->q.first_byte_at = ctx->q.last_op; - } - ctx->q.last_io = ctx->q.last_op; - } - return CURLE_OK; - } -} - -/** - * Read a network packet to send from ngtcp2 into `buf`. - * Return number of bytes written or -1 with *err set. - */ -static CURLcode read_pkt_to_send(void *userp, - unsigned char *buf, size_t buflen, - size_t *pnread) -{ - struct pkt_io_ctx *x = userp; - struct cf_ngtcp2_ctx *ctx = x->cf->ctx; - nghttp3_vec vec[16]; - nghttp3_ssize veccnt; - ngtcp2_ssize ndatalen; - uint32_t flags; - int64_t stream_id; - int fin; - ssize_t n; - - *pnread = 0; - veccnt = 0; - stream_id = -1; - fin = 0; - - /* ngtcp2 may want to put several frames from different streams into - * this packet. `NGTCP2_WRITE_STREAM_FLAG_MORE` tells it to do so. - * When `NGTCP2_ERR_WRITE_MORE` is returned, we *need* to make - * another iteration. - * When ngtcp2 is happy (because it has no other frame that would fit - * or it has nothing more to send), it returns the total length - * of the assembled packet. This may be 0 if there was nothing to send. */ - for(;;) { - - if(ctx->h3conn && ngtcp2_conn_get_max_data_left(ctx->qconn)) { - veccnt = nghttp3_conn_writev_stream(ctx->h3conn, &stream_id, &fin, vec, - CURL_ARRAYSIZE(vec)); - if(veccnt < 0) { - failf(x->data, "nghttp3_conn_writev_stream returned error: %s", - nghttp3_strerror((int)veccnt)); - cf_ngtcp2_h3_err_set(x->cf, x->data, (int)veccnt); - return CURLE_SEND_ERROR; - } - } - - flags = NGTCP2_WRITE_STREAM_FLAG_MORE | - (fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0); - n = ngtcp2_conn_writev_stream(ctx->qconn, &x->ps.path, - NULL, buf, buflen, - &ndatalen, flags, stream_id, - (const ngtcp2_vec *)vec, veccnt, x->ts); - if(n == 0) { - /* nothing to send */ - return CURLE_AGAIN; - } - else if(n < 0) { - switch(n) { - case NGTCP2_ERR_STREAM_DATA_BLOCKED: { - struct h3_stream_ctx *stream; - DEBUGASSERT(ndatalen == -1); - nghttp3_conn_block_stream(ctx->h3conn, stream_id); - CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] block quic flow", - stream_id); - stream = cf_ngtcp2_get_stream(ctx, stream_id); - if(stream) /* it might be not one of our h3 streams? */ - stream->quic_flow_blocked = TRUE; - n = 0; - break; - } - case NGTCP2_ERR_STREAM_SHUT_WR: - DEBUGASSERT(ndatalen == -1); - nghttp3_conn_shutdown_stream_write(ctx->h3conn, stream_id); - n = 0; - break; - case NGTCP2_ERR_WRITE_MORE: - /* ngtcp2 wants to send more. update the flow of the stream whose data - * is in the buffer and continue */ - DEBUGASSERT(ndatalen >= 0); - n = 0; - break; - default: - DEBUGASSERT(ndatalen == -1); - failf(x->data, "ngtcp2_conn_writev_stream returned error: %s", - ngtcp2_strerror((int)n)); - cf_ngtcp2_err_set(x->cf, x->data, (int)n); - return CURLE_SEND_ERROR; - } - } - - if(ndatalen >= 0) { - /* we add the amount of data bytes to the flow windows */ - int rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen); - if(rv) { - failf(x->data, "nghttp3_conn_add_write_offset returned error: %s", - nghttp3_strerror(rv)); - return CURLE_SEND_ERROR; - } - } - - if(n > 0) { - /* packet assembled, leave */ - *pnread = (size_t)n; - return CURLE_OK; - } - } -} - -static CURLcode cf_progress_egress(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - size_t nread; - size_t max_payload_size, path_max_payload_size; - size_t pktcnt = 0; - size_t gsolen = 0; /* this disables gso until we have a clue */ - size_t send_quantum; - CURLcode result; - struct pkt_io_ctx local_pktx; - - if(!pktx) { - pktx_init(&local_pktx, cf, data); - pktx = &local_pktx; - } - else { - pktx_update_time(data, pktx, cf); - ngtcp2_path_storage_zero(&pktx->ps); - } - - result = vquic_flush(cf, data, &ctx->q); - if(result) { - if(result == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); - return CURLE_OK; - } - return result; - } - - /* In UDP, there is a maximum theoretical packet payload length and - * a minimum payload length that is "guaranteed" to work. - * To detect if this minimum payload can be increased, ngtcp2 sends - * now and then a packet payload larger than the minimum. It that - * is ACKed by the peer, both parties know that it works and - * the subsequent packets can use a larger one. - * This is called PMTUD (Path Maximum Transmission Unit Discovery). - * Since a PMTUD might be rejected right on send, we do not want it - * be followed by other packets of lesser size. Because those would - * also fail then. If we detect a PMTUD while buffering, we flush. - */ - max_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(ctx->qconn); - path_max_payload_size = - ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn); - send_quantum = ngtcp2_conn_get_send_quantum(ctx->qconn); - CURL_TRC_CF(data, cf, "egress, collect and send packets, quantum=%zu", - send_quantum); - for(;;) { - /* add the next packet to send, if any, to our buffer */ - result = Curl_bufq_sipn(&ctx->q.sendbuf, max_payload_size, - read_pkt_to_send, pktx, &nread); - if(result == CURLE_AGAIN) - break; - else if(result) - return result; - else { - size_t buflen = Curl_bufq_len(&ctx->q.sendbuf); - if((buflen >= send_quantum) || - ((buflen + gsolen) >= ctx->q.sendbuf.chunk_size)) - break; - DEBUGASSERT(nread > 0); - ++pktcnt; - if(pktcnt == 1) { - /* first packet in buffer. This is either of a known, "good" - * payload size or it is a PMTUD. We shall see. */ - gsolen = nread; - } - else if(nread > gsolen || - (gsolen > path_max_payload_size && nread != gsolen)) { - /* The added packet is a PMTUD *or* the one(s) before the - * added were PMTUD and the last one is smaller. - * Flush the buffer before the last add. */ - result = vquic_send_tail_split(cf, data, &ctx->q, - gsolen, nread, nread); - if(result) { - if(result == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); - return CURLE_OK; - } - return result; - } - pktcnt = 0; - } - else if(nread < gsolen) { - /* Reached capacity of our buffer *or* - * last add was shorter than the previous ones, flush */ - break; - } - } - } - - if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { - /* time to send */ - CURL_TRC_CF(data, cf, "egress, send collected %zu packets in %zu bytes", - pktcnt, Curl_bufq_len(&ctx->q.sendbuf)); - result = vquic_send(cf, data, &ctx->q, gsolen); - if(result) { - if(result == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); - return CURLE_OK; - } - return result; - } - pktx_update_time(data, pktx, cf); - ngtcp2_conn_update_pkt_tx_time(ctx->qconn, pktx->ts); - } - return CURLE_OK; -} - static CURLcode h3_data_pause(struct Curl_cfilter *cf, struct Curl_easy *data, bool pause) @@ -2226,7 +909,7 @@ static CURLcode cf_ngtcp2_cntrl(struct Curl_cfilter *cf, result = h3_data_pause(cf, data, (arg1 != 0)); break; case CF_CTRL_DATA_DONE: - h3_data_done(cf, data); + Curl_cf_ngtcp2_h3_stream_done(cf, data); break; case CF_CTRL_DATA_DONE_SEND: { struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); @@ -2262,7 +945,7 @@ static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx) } ctx->qlogfd = -1; Curl_vquic_tls_cleanup(&ctx->tls); - Curl_ssl_peer_cleanup(&ctx->peer); + Curl_ssl_peer_cleanup(&ctx->ssl_peer); vquic_ctx_free(&ctx->q); if(ctx->h3conn) { nghttp3_conn_del(ctx->h3conn); @@ -2281,114 +964,6 @@ static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx) ctx->call_data = save; } -static CURLcode cf_ngtcp2_shutdown(struct Curl_cfilter *cf, - struct Curl_easy *data, bool *done) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct cf_call_data save; - struct pkt_io_ctx pktx; - CURLcode result = CURLE_OK; - - if(cf->shutdown || !ctx->qconn) { - *done = TRUE; - return CURLE_OK; - } - - if(!cf->next) { - Curl_bufq_reset(&ctx->q.sendbuf); - *done = TRUE; - return CURLE_OK; - } - - CF_DATA_SAVE(save, cf, data); - *done = FALSE; - pktx_init(&pktx, cf, data); - - if(!ctx->shutdown_started) { - char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; - ngtcp2_ssize nwritten; - - if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { - CURL_TRC_CF(data, cf, "shutdown, flushing sendbuf"); - result = cf_progress_egress(cf, data, &pktx); - if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { - CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); - result = CURLE_OK; - goto out; - } - else if(result) { - CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); - *done = TRUE; - goto out; - } - } - - DEBUGASSERT(Curl_bufq_is_empty(&ctx->q.sendbuf)); - ctx->shutdown_started = TRUE; - nwritten = ngtcp2_conn_write_connection_close( - ctx->qconn, NULL, /* path */ - NULL, /* pkt_info */ - (uint8_t *)buffer, sizeof(buffer), - &ctx->last_error, pktx.ts); - CURL_TRC_CF(data, cf, "start shutdown(err_type=%d, err_code=%" - PRIu64 ") -> %zd", ctx->last_error.type, - ctx->last_error.error_code, (ssize_t)nwritten); - /* there are cases listed in ngtcp2 documentation where this call - * may fail. Since we are doing a connection shutdown as graceful - * as we can, such an error is ignored here. */ - if(nwritten > 0) { - /* Ignore amount written. sendbuf was empty and has always room for - * NGTCP2_MAX_UDP_PAYLOAD_SIZE. It can only completely fail, in which - * case `result` is set non zero. */ - size_t n; - result = Curl_bufq_write(&ctx->q.sendbuf, (const unsigned char *)buffer, - (size_t)nwritten, &n); - if(result) { - CURL_TRC_CF(data, cf, "error %d adding shutdown packets to sendbuf, " - "aborting shutdown", result); - goto out; - } - - ctx->q.no_gso = TRUE; - ctx->q.gsolen = (size_t)nwritten; - ctx->q.split_len = 0; - } - } - - if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { - CURL_TRC_CF(data, cf, "shutdown, flushing egress"); - result = vquic_flush(cf, data, &ctx->q); - if(result == CURLE_AGAIN) { - CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); - result = CURLE_OK; - goto out; - } - else if(result) { - CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); - *done = TRUE; - goto out; - } - } - - if(Curl_bufq_is_empty(&ctx->q.sendbuf)) { - /* Sent everything off. ngtcp2 seems to have no support for graceful - * shutdowns. We are done. */ - CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); - *done = TRUE; - result = CURLE_OK; - } -out: - CF_DATA_RESTORE(cf, save); - return result; -} - -static void cf_ngtcp2_conn_close(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - bool done; - cf_ngtcp2_shutdown(cf, data, &done); -} - static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; @@ -2398,552 +973,21 @@ static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) if(ctx->qconn) { struct cf_call_data save; CF_DATA_SAVE(save, cf, data); - cf_ngtcp2_conn_close(cf, data); + Curl_cf_ngtcp2_cmn_conn_close(cf, data); cf_ngtcp2_ctx_close(ctx); CF_DATA_RESTORE(cf, save); } - cf_ngtcp2_ctx_free(cf->ctx); + Curl_cf_ngtcp2_ctx_cleanup(ctx); + curlx_free(ctx); cf->ctx = NULL; } } -#ifdef USE_OPENSSL -/* The "new session" callback must return zero if the session can be removed - * or non-zero if the session has been put into the session cache. - */ -static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) -{ - struct Curl_cfilter *cf; - struct cf_ngtcp2_ctx *ctx; - struct Curl_easy *data; - ngtcp2_crypto_conn_ref *cref; - - cref = (ngtcp2_crypto_conn_ref *)SSL_get_app_data(ssl); - cf = cref ? cref->user_data : NULL; - ctx = cf ? cf->ctx : NULL; - data = cf ? CF_DATA_CURRENT(cf) : NULL; - if(cf && data && ctx) { - unsigned char *quic_tp = NULL; - size_t quic_tp_len = 0; -#ifdef HAVE_OPENSSL_EARLYDATA - ngtcp2_ssize tplen; - uint8_t tpbuf[256]; - - tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, - sizeof(tpbuf)); - if(tplen < 0) - CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", - ngtcp2_strerror((int)tplen)); - else { - quic_tp = (unsigned char *)tpbuf; - quic_tp_len = (size_t)tplen; - } -#endif - Curl_ossl_add_session(cf, data, ctx->peer.scache_key, ssl_sessionid, - SSL_version(ssl), "h3", quic_tp, quic_tp_len); - } - return 0; -} -#endif /* USE_OPENSSL */ - -#ifdef USE_GNUTLS - -#ifdef CURLVERBOSE -static const char *gtls_hs_msg_name(int mtype) -{ - switch(mtype) { - case 1: - return "ClientHello"; - case 2: - return "ServerHello"; - case 4: - return "SessionTicket"; - case 8: - return "EncryptedExtensions"; - case 11: - return "Certificate"; - case 13: - return "CertificateRequest"; - case 15: - return "CertificateVerify"; - case 20: - return "Finished"; - case 24: - return "KeyUpdate"; - case 254: - return "MessageHash"; - } - return "Unknown"; -} -#endif - -static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype, - unsigned when, unsigned int incoming, - const gnutls_datum_t *msg) -{ - ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); - struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL; - struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; - - (void)msg; - (void)incoming; - if(when && cf && ctx) { /* after message has been processed */ - struct Curl_easy *data = CF_DATA_CURRENT(cf); - DEBUGASSERT(data); - if(!data) - return 0; - CURL_TRC_CF(data, cf, "SSL message: %s %s [%u]", - incoming ? "<-" : "->", gtls_hs_msg_name(htype), htype); - switch(htype) { - case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: { - ngtcp2_ssize tplen; - uint8_t tpbuf[256]; - unsigned char *quic_tp = NULL; - size_t quic_tp_len = 0; - - tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, - sizeof(tpbuf)); - if(tplen < 0) - CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", - ngtcp2_strerror((int)tplen)); - else { - quic_tp = (unsigned char *)tpbuf; - quic_tp_len = (size_t)tplen; - } - (void)Curl_gtls_cache_session(cf, data, ctx->peer.scache_key, - session, 0, "h3", quic_tp, quic_tp_len); - break; - } - default: - break; - } - } - return 0; -} -#endif /* USE_GNUTLS */ - -#ifdef USE_WOLFSSL -static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) -{ - ngtcp2_crypto_conn_ref *conn_ref = wolfSSL_get_app_data(ssl); - struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL; - - DEBUGASSERT(cf); - if(cf && session) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = CF_DATA_CURRENT(cf); - DEBUGASSERT(data); - if(data && ctx) { - ngtcp2_ssize tplen; - uint8_t tpbuf[256]; - unsigned char *quic_tp = NULL; - size_t quic_tp_len = 0; - - tplen = ngtcp2_conn_encode_0rtt_transport_params(ctx->qconn, tpbuf, - sizeof(tpbuf)); - if(tplen < 0) - CURL_TRC_CF(data, cf, "error encoding 0RTT transport data: %s", - ngtcp2_strerror((int)tplen)); - else { - quic_tp = (unsigned char *)tpbuf; - quic_tp_len = (size_t)tplen; - } - (void)Curl_wssl_cache_session(cf, data, ctx->peer.scache_key, - session, wolfSSL_version(ssl), - "h3", quic_tp, quic_tp_len); - } - } - return 0; -} -#endif /* USE_WOLFSSL */ - -static CURLcode cf_ngtcp2_tls_ctx_setup(struct Curl_cfilter *cf, - struct Curl_easy *data, - void *user_data) -{ - struct curl_tls_ctx *ctx = user_data; - -#ifdef USE_OPENSSL -#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL) - if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ossl.ssl_ctx) - != 0) { - failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); - return CURLE_FAILED_INIT; - } -#elif defined(OPENSSL_QUIC_API2) - /* nothing to do */ -#else - if(ngtcp2_crypto_quictls_configure_client_context(ctx->ossl.ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); - return CURLE_FAILED_INIT; - } -#endif /* !OPENSSL_IS_AWSLC && !OPENSSL_IS_BORINGSSL */ - if(Curl_ssl_scache_use(cf, data)) { - /* Enable the session cache because it is a prerequisite for the - * "new session" callback. Use the "external storage" mode to prevent - * OpenSSL from creating an internal session cache. - */ - SSL_CTX_set_session_cache_mode(ctx->ossl.ssl_ctx, - SSL_SESS_CACHE_CLIENT | - SSL_SESS_CACHE_NO_INTERNAL); - SSL_CTX_sess_set_new_cb(ctx->ossl.ssl_ctx, quic_ossl_new_session_cb); - } - -#elif defined(USE_GNUTLS) - if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls.session) != 0) { - failf(data, "ngtcp2_crypto_gnutls_configure_client_session failed"); - return CURLE_FAILED_INIT; - } - if(Curl_ssl_scache_use(cf, data)) { - gnutls_handshake_set_hook_function(ctx->gtls.session, - GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, - quic_gtls_handshake_cb); - } - -#elif defined(USE_WOLFSSL) - if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); - return CURLE_FAILED_INIT; - } - if(Curl_ssl_scache_use(cf, data)) { - /* Register to get notified when a new session is received */ - wolfSSL_CTX_sess_set_new_cb(ctx->wssl.ssl_ctx, wssl_quic_new_session_cb); - } -#endif - return CURLE_OK; -} - -static CURLcode cf_ngtcp2_on_session_reuse(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct alpn_spec *alpns, - struct Curl_ssl_session *scs, - bool *do_early_data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - - *do_early_data = FALSE; -#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) - ctx->earlydata_max = scs->earlydata_max; -#endif -#ifdef USE_GNUTLS - ctx->earlydata_max = - gnutls_record_get_max_early_data_size(ctx->tls.gtls.session); -#endif -#ifdef USE_WOLFSSL -#ifdef WOLFSSL_EARLY_DATA - ctx->earlydata_max = scs->earlydata_max; -#else - ctx->earlydata_max = 0; -#endif /* WOLFSSL_EARLY_DATA */ -#endif -#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ - (defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA)) - if(!ctx->earlydata_max) { - CURL_TRC_CF(data, cf, "SSL session does not allow earlydata"); - } - else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) { - CURL_TRC_CF(data, cf, "SSL session from different ALPN, no early data"); - } - else if(!scs->quic_tp || !scs->quic_tp_len) { - CURL_TRC_CF(data, cf, "no 0RTT transport parameters, no early data"); - } - else { - int rv; - rv = ngtcp2_conn_decode_and_set_0rtt_transport_params( - ctx->qconn, (const uint8_t *)scs->quic_tp, scs->quic_tp_len); - if(rv) - CURL_TRC_CF(data, cf, "no early data, failed to set 0RTT transport " - "parameters: %s", ngtcp2_strerror(rv)); - else { - infof(data, "SSL session allows %zu bytes of early data, " - "reusing ALPN '%s'", ctx->earlydata_max, scs->alpn); - result = init_ngh3_conn(cf, data); - if(!result) { - ctx->use_earlydata = TRUE; - cf->connected = TRUE; - *do_early_data = TRUE; - } - } - } -#else /* not supported in the TLS backend */ - (void)data; - (void)ctx; - (void)scs; - (void)alpns; -#endif - return result; -} - -static bool cf_ngtcp2_need_httpsrr(struct Curl_easy *data) -{ -#ifdef USE_OPENSSL - return Curl_ossl_need_httpsrr(data); -#elif defined(USE_WOLFSSL) - return Curl_wssl_need_httpsrr(data); -#else - (void)data; - return FALSE; -#endif -} - -/* - * Might be called twice for happy eyeballs. - */ -static CURLcode cf_connect_start(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct pkt_io_ctx *pktx) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - int rc; - int rv; - CURLcode result; - const struct Curl_sockaddr_ex *sockaddr = NULL; - int qfd; - static const struct alpn_spec ALPN_SPEC_H3 = { { "h3", "h3-29" }, 2 }; - - DEBUGASSERT(ctx->initialized); - ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; - result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN); - if(result) - return result; - - ctx->scid.datalen = NGTCP2_MAX_CIDLEN; - result = Curl_rand(data, ctx->scid.data, NGTCP2_MAX_CIDLEN); - if(result) - return result; - - (void)Curl_qlogdir(data, ctx->scid.data, NGTCP2_MAX_CIDLEN, &qfd); - ctx->qlogfd = qfd; /* -1 if failure above */ - quic_settings(ctx, data, pktx); - - result = vquic_ctx_init(data, &ctx->q); - if(result) - return result; - - /* Query socket and remote address from sub-chain */ - if(Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL)) { - /* No direct socket - must be tunneled QUIC (CONNECT-UDP through proxy) */ - ctx->q.sockfd = CURL_SOCKET_BAD; - } - - if(ctx->q.sockfd != CURL_SOCKET_BAD) { - /* Direct UDP socket - get local address for ngtcp2 */ - ctx->q.local_addrlen = sizeof(ctx->q.local_addr); - rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, - &ctx->q.local_addrlen); - if(rv == -1) - return CURLE_QUIC_CONNECT_ERROR; - - ngtcp2_addr_init(&ctx->connected_path.local, - (struct sockaddr *)&ctx->q.local_addr, - ctx->q.local_addrlen); - ngtcp2_addr_init(&ctx->connected_path.remote, - &sockaddr->curl_sa_addr, (socklen_t)sockaddr->addrlen); - - rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, - &ctx->connected_path, - NGTCP2_PROTO_VER_V1, &ng_callbacks, - &ctx->settings, &ctx->transport_params, - Curl_ngtcp2_mem(), cf); - if(rc) - return CURLE_QUIC_CONNECT_ERROR; - - ctx->conn_ref.get_conn = get_conn; - ctx->conn_ref.user_data = cf; - } - else { - /* Tunneled QUIC (e.g. CONNECT-UDP): get remote address - from the connected filter below */ - const struct Curl_sockaddr_ex *remote = NULL; - if(cf->next->cft->query(cf->next, data, CF_QUERY_REMOTE_ADDR, NULL, - CURL_UNCONST(&remote))) - return CURLE_QUIC_CONNECT_ERROR; - if(!remote) - return CURLE_QUIC_CONNECT_ERROR; - - memset(&ctx->q.local_addr, 0, sizeof(ctx->q.local_addr)); - switch(remote->family) { - case AF_INET: - ((struct sockaddr_in *)&ctx->q.local_addr)->sin_family = AF_INET; - ctx->q.local_addrlen = sizeof(struct sockaddr_in); - break; -#ifdef USE_IPV6 - case AF_INET6: - ((struct sockaddr_in6 *)&ctx->q.local_addr)->sin6_family = AF_INET6; - ctx->q.local_addrlen = sizeof(struct sockaddr_in6); - break; -#endif - default: - return CURLE_QUIC_CONNECT_ERROR; - } - - ngtcp2_addr_init(&ctx->connected_path.local, - (struct sockaddr *)&ctx->q.local_addr, - ctx->q.local_addrlen); - ngtcp2_addr_init(&ctx->connected_path.remote, - &remote->curl_sa_addr, - (socklen_t)remote->addrlen); - - rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, - &ctx->connected_path, - NGTCP2_PROTO_VER_V1, &ng_callbacks, - &ctx->settings, &ctx->transport_params, - Curl_ngtcp2_mem(), cf); - if(rc) - return CURLE_QUIC_CONNECT_ERROR; - - ctx->conn_ref.get_conn = get_conn; - ctx->conn_ref.user_data = cf; - } - - result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, &ALPN_SPEC_H3, - cf_ngtcp2_tls_ctx_setup, &ctx->tls, - &ctx->conn_ref, - cf_ngtcp2_on_session_reuse); - if(result) - return result; - -#if defined(USE_OPENSSL) && defined(OPENSSL_QUIC_API2) - if(ngtcp2_crypto_ossl_ctx_new(&ctx->ossl_ctx, ctx->tls.ossl.ssl) != 0) { - failf(data, "ngtcp2_crypto_ossl_ctx_new failed"); - return CURLE_FAILED_INIT; - } - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ossl_ctx); - if(ngtcp2_crypto_ossl_configure_client_session(ctx->tls.ossl.ssl) != 0) { - failf(data, "ngtcp2_crypto_ossl_configure_client_session failed"); - return CURLE_FAILED_INIT; - } -#elif defined(USE_OPENSSL) - SSL_set_quic_use_legacy_codepoint(ctx->tls.ossl.ssl, 0); - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ossl.ssl); -#elif defined(USE_GNUTLS) - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls.session); -#elif defined(USE_WOLFSSL) - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.wssl.ssl); -#else -#error "ngtcp2 TLS backend not defined" -#endif - - ngtcp2_ccerr_default(&ctx->last_error); - - return CURLE_OK; -} - static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) { - struct cf_ngtcp2_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - struct cf_call_data save; - struct pkt_io_ctx pktx; - - if(cf->connected) { - *done = TRUE; - return CURLE_OK; - } - - /* Connect the sub-chain */ - if(cf->next && !cf->next->connected) { - result = Curl_conn_cf_connect(cf->next, data, done); - if(result || !*done) - return result; - } - - *done = FALSE; - - if(cf_ngtcp2_need_httpsrr(data) && - !Curl_conn_dns_resolved_https(data, cf->sockindex)) { - CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); - return CURLE_OK; - } - - pktx_init(&pktx, cf, data); - CF_DATA_SAVE(save, cf, data); - - if(!ctx->qconn) { - ctx->started_at = *Curl_pgrs_now(data); - result = cf_connect_start(cf, data, &pktx); - if(result) - goto out; - if(cf->connected) { - *done = TRUE; - goto out; - } - result = cf_progress_egress(cf, data, &pktx); - /* we do not expect to be able to recv anything yet */ - goto out; - } - - result = cf_progress_ingress(cf, data, &pktx); - if(result) - goto out; - - result = cf_progress_egress(cf, data, &pktx); - if(result) - goto out; - - if(ngtcp2_conn_get_handshake_completed(ctx->qconn)) { - result = ctx->tls_vrfy_result; - if(!result) { - CURL_TRC_CF(data, cf, "peer verified"); - cf->connected = TRUE; - *done = TRUE; - } - } - -out: - if(ctx->tls_vrfy_result) - result = ctx->tls_vrfy_result; - if(ctx->qconn && - ((result == CURLE_RECV_ERROR) || (result == CURLE_SEND_ERROR)) && - ngtcp2_conn_in_draining_period(ctx->qconn)) { - const ngtcp2_ccerr *cerr = ngtcp2_conn_get_ccerr(ctx->qconn); - - result = CURLE_COULDNT_CONNECT; - if(cerr) { - CURL_TRC_CF(data, cf, "connect error, type=%d, code=%" PRIu64, - cerr->type, cerr->error_code); - switch(cerr->type) { - case NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION: - CURL_TRC_CF(data, cf, "error in version negotiation"); - break; - default: - if(cerr->error_code >= NGTCP2_CRYPTO_ERROR) { - CURL_TRC_CF(data, cf, "crypto error, tls alert=%u", - (unsigned int)(cerr->error_code & 0xffU)); - } - else if(cerr->error_code == NGTCP2_CONNECTION_REFUSED) { - CURL_TRC_CF(data, cf, "connection refused by server"); - /* When a QUIC server instance is shutting down, it may send us a - * CONNECTION_CLOSE with this code right away. We want - * to keep on trying in this case. */ - result = CURLE_WEIRD_SERVER_REPLY; - } - } - } - } - -#ifdef CURLVERBOSE - if(result) { - if(ctx->q.sockfd != CURL_SOCKET_BAD) { - /* Direct UDP socket - get IP info for error reporting */ - struct ip_quadruple ip; - - if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip)) - infof(data, "QUIC connect to %s port %u failed: %s", - ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); - } - } -#endif - if(!result && ctx->qconn) { - result = check_and_set_expiry(cf, data, &pktx); - } - if(result || *done) - CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); - CF_DATA_RESTORE(cf, save); - return result; + return Curl_cf_ngtcp2_cmn_connect(cf, data, done); } static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, @@ -3026,73 +1070,27 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, CURLE_UNKNOWN_OPTION; } -static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool *input_pending) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - bool alive = FALSE; - const ngtcp2_transport_params *rp; - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - *input_pending = FALSE; - if(!ctx->qconn || ctx->shutdown_started) - goto out; - - /* We do not announce a max idle timeout, but when the peer does - * it closes the connection when it expires. */ - rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); - if(rp && rp->max_idle_timeout) { - timediff_t idletime_ms = - curlx_ptimediff_ms(Curl_pgrs_now(data), &ctx->q.last_io); - if(idletime_ms > 0) { - uint64_t max_idle_ms = - (uint64_t)(rp->max_idle_timeout / NGTCP2_MILLISECONDS); - if((uint64_t)idletime_ms > max_idle_ms) - goto out; - } - } - - if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) - goto out; - - alive = TRUE; - if(*input_pending) { - CURLcode result; - /* This happens before we have sent off a request and the connection is - not in use by any other transfer, there should not be any data here, - only "protocol frames" */ - *input_pending = FALSE; - result = cf_progress_ingress(cf, data, NULL); - CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); - alive = result ? FALSE : TRUE; - } - -out: - CF_DATA_RESTORE(cf, save); - return alive; -} - struct Curl_cftype Curl_cft_http3 = { "HTTP/3", CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX | CF_TYPE_HTTP, 0, cf_ngtcp2_destroy, cf_ngtcp2_connect, - cf_ngtcp2_shutdown, + Curl_cf_ngtcp2_cmn_shutdown, cf_ngtcp2_adjust_pollset, Curl_cf_def_data_pending, cf_ngtcp2_send, cf_ngtcp2_recv, cf_ngtcp2_cntrl, - cf_ngtcp2_conn_is_alive, + Curl_cf_ngtcp2_cmn_conn_is_alive, Curl_cf_def_conn_keep_alive, cf_ngtcp2_query, }; CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr) { @@ -3105,15 +1103,16 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, result = CURLE_OUT_OF_MEMORY; goto out; } - cf_ngtcp2_ctx_init(ctx); - - result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + result = Curl_cf_ngtcp2_ctx_init(ctx, origin, peer, + &conn->ssl_config, init_ngh3_conn); + if(!result) + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); if(result) goto out; cf->conn = conn; - result = Curl_cf_udp_create(&cf->next, data, conn, addr, - TRNSPRT_QUIC, TRNSPRT_QUIC); + result = Curl_cf_udp_create(&cf->next, data, origin, peer, TRNSPRT_QUIC, + conn, addr, NULL, TRNSPRT_QUIC); if(result) goto out; cf->next->conn = cf->conn; @@ -3124,13 +1123,17 @@ out: if(result) { if(cf) Curl_conn_cf_discard_chain(&cf, data); - else if(ctx) - cf_ngtcp2_ctx_free(ctx); + else if(ctx) { + Curl_cf_ngtcp2_ctx_cleanup(ctx); + curlx_free(ctx); + } } return result; } -CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at) +CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at, + struct Curl_peer *origin, + struct Curl_peer *peer) { struct cf_ngtcp2_ctx *ctx = NULL; struct Curl_cfilter *cf = NULL; @@ -3141,17 +1144,20 @@ CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at) result = CURLE_OUT_OF_MEMORY; goto out; } - cf_ngtcp2_ctx_init(ctx); - - result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + result = Curl_cf_ngtcp2_ctx_init(ctx, origin, peer, + &cf_at->conn->ssl_config, init_ngh3_conn); + if(!result) + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); if(result) goto out; Curl_conn_cf_insert_after(cf_at, cf); - cf->conn = cf_at->conn; out: if(result) { curlx_safefree(cf); - cf_ngtcp2_ctx_free(ctx); + if(ctx) { + Curl_cf_ngtcp2_ctx_cleanup(ctx); + curlx_free(ctx); + } } return result; } diff --git a/lib/vquic/cf-ngtcp2.h b/lib/vquic/cf-ngtcp2.h index d69ae08eae..601efc8224 100644 --- a/lib/vquic/cf-ngtcp2.h +++ b/lib/vquic/cf-ngtcp2.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_VQUIC_CURL_NGTCP2_H -#define HEADER_CURL_VQUIC_CURL_NGTCP2_H +#ifndef HEADER_CURL_VQUIC_CF_NGTCP2_H +#define HEADER_CURL_VQUIC_CF_NGTCP2_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -48,14 +48,16 @@ struct Curl_cfilter; #include "urldata.h" -void Curl_ngtcp2_ver(char *p, size_t len); - CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr); -CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at); +CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at, + struct Curl_peer *origin, + struct Curl_peer *peer); #endif -#endif /* HEADER_CURL_VQUIC_CURL_NGTCP2_H */ +#endif /* HEADER_CURL_VQUIC_CF_NGTCP2_H */ diff --git a/lib/vquic/cf-quiche.c b/lib/vquic/cf-quiche.c index 5736341e1a..3b568c1965 100644 --- a/lib/vquic/cf-quiche.c +++ b/lib/vquic/cf-quiche.c @@ -75,7 +75,7 @@ void Curl_quiche_ver(char *p, size_t len) struct cf_quiche_ctx { struct cf_quic_ctx q; - struct ssl_peer peer; + struct ssl_peer ssl_peer; struct curl_tls_ctx tls; quiche_conn *qconn; quiche_config *cfg; @@ -106,7 +106,10 @@ static void quiche_debug_log(const char *line, void *argp) static void h3_stream_hash_free(unsigned int id, void *stream); -static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx) +static CURLcode cf_quiche_ctx_init(struct cf_quiche_ctx *ctx, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc) { DEBUGASSERT(!ctx->initialized); #ifdef DEBUG_QUICHE @@ -121,6 +124,7 @@ static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx) BUFQ_OPT_SOFT_LIMIT); ctx->data_recvd = 0; ctx->initialized = TRUE; + return Curl_vquic_tls_peer_init(origin, peer, sslc, &ctx->ssl_peer); } static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx) @@ -129,7 +133,7 @@ static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx) /* quiche freed it */ ctx->tls.ossl.ssl = NULL; Curl_vquic_tls_cleanup(&ctx->tls); - Curl_ssl_peer_cleanup(&ctx->peer); + Curl_ssl_peer_cleanup(&ctx->ssl_peer); vquic_ctx_free(&ctx->q); Curl_uint32_hash_destroy(&ctx->streams); curlx_dyn_free(&ctx->h1hdr); @@ -156,7 +160,7 @@ static void cf_quiche_ctx_close(struct cf_quiche_ctx *ctx) quiche_config_free(ctx->cfg); ctx->cfg = NULL; } - Curl_ssl_peer_cleanup(&ctx->peer); + Curl_ssl_peer_cleanup(&ctx->ssl_peer); } static CURLcode cf_flush_egress(struct Curl_cfilter *cf, @@ -1291,7 +1295,7 @@ static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf, sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); - result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->ssl_peer, &ALPN_SPEC_H3, NULL, NULL, cf, NULL); if(result) return result; @@ -1357,7 +1361,7 @@ static CURLcode cf_quiche_verify_peer(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_quiche_ctx *ctx = cf->ctx; - return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->ssl_peer); } static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, @@ -1629,6 +1633,8 @@ struct Curl_cftype Curl_cft_http3 = { CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr) { @@ -1641,15 +1647,15 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, result = CURLE_OUT_OF_MEMORY; goto out; } - cf_quiche_ctx_init(ctx); - - result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + result = cf_quiche_ctx_init(ctx, origin, peer, &conn->ssl_config); + if(!result) + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); if(result) goto out; cf->conn = conn; - result = Curl_cf_udp_create(&cf->next, data, conn, addr, - TRNSPRT_QUIC, TRNSPRT_QUIC); + result = Curl_cf_udp_create(&cf->next, data, origin, peer, TRNSPRT_QUIC, + conn, addr, NULL, TRNSPRT_QUIC); if(result) goto out; cf->next->conn = cf->conn; @@ -1667,4 +1673,34 @@ out: return result; } +CURLcode Curl_cf_quiche_insert_after(struct Curl_cfilter *cf_at, + struct Curl_peer *origin, + struct Curl_peer *peer) +{ + struct cf_quiche_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + ctx = curlx_calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + result = cf_quiche_ctx_init(ctx, origin, peer, &cf_at->conn->ssl_config); + if(!result) + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + if(result) + goto out; + Curl_conn_cf_insert_after(cf_at, cf); + +out: + if(result) { + curlx_safefree(cf); + if(ctx) + cf_quiche_ctx_free(ctx); + } + + return result; +} + #endif diff --git a/lib/vquic/cf-quiche.h b/lib/vquic/cf-quiche.h index c2c88ddeaf..88d9161dd7 100644 --- a/lib/vquic/cf-quiche.h +++ b/lib/vquic/cf-quiche.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_VQUIC_CURL_QUICHE_H -#define HEADER_CURL_VQUIC_CURL_QUICHE_H +#ifndef HEADER_CURL_VQUIC_CF_QUICHE_H +#define HEADER_CURL_VQUIC_CF_QUICHE_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -37,9 +37,14 @@ void Curl_quiche_ver(char *p, size_t len); CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr); +CURLcode Curl_cf_quiche_insert_after(struct Curl_cfilter *cf_at, + struct Curl_peer *origin, + struct Curl_peer *peer); #endif -#endif /* HEADER_CURL_VQUIC_CURL_QUICHE_H */ +#endif /* HEADER_CURL_VQUIC_CF_QUICHE_H */ diff --git a/lib/vquic/vquic-tls.c b/lib/vquic/vquic-tls.c index 00366b7d30..58f139306a 100644 --- a/lib/vquic/vquic-tls.c +++ b/lib/vquic/vquic-tls.c @@ -49,17 +49,12 @@ #include "vtls/vtls_scache.h" #include "vquic/vquic-tls.h" -CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, - struct Curl_cfilter *cf, - struct Curl_easy *data, - struct ssl_peer *peer, - const struct alpn_spec *alpns, - Curl_vquic_tls_ctx_setup *cb_setup, - void *cb_user_data, void *ssl_user_data, - Curl_vquic_session_reuse_cb *session_reuse_cb) +CURLcode Curl_vquic_tls_peer_init(struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, + struct ssl_peer *ssl_peer) { char tls_id[80]; - CURLcode result; #ifdef USE_OPENSSL Curl_ossl_version(tls_id, sizeof(tls_id)); @@ -71,24 +66,31 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, #error "no TLS lib in used, should not happen" return CURLE_FAILED_INIT; #endif - (void)session_reuse_cb; - if(peer->dest) - Curl_ssl_peer_cleanup(peer); - result = Curl_ssl_peer_init(peer, cf, tls_id, TRNSPRT_QUIC); - if(result) - return result; + if(ssl_peer->origin || ssl_peer->peer) + Curl_ssl_peer_cleanup(ssl_peer); + return Curl_ssl_peer_init(ssl_peer, origin, peer, sslc, + tls_id, TRNSPRT_QUIC); +} +CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *ssl_peer, + const struct alpn_spec *alpns, + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data, void *ssl_user_data, + Curl_vquic_session_reuse_cb *session_reuse_cb) +{ #ifdef USE_OPENSSL - (void)result; - return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer, alpns, + return Curl_ossl_ctx_init(&ctx->ossl, cf, data, ssl_peer, alpns, cb_setup, cb_user_data, NULL, ssl_user_data, session_reuse_cb); #elif defined(USE_GNUTLS) - return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer, alpns, + return Curl_gtls_ctx_init(&ctx->gtls, cf, data, ssl_peer, alpns, cb_setup, cb_user_data, ssl_user_data, session_reuse_cb); #elif defined(USE_WOLFSSL) - return Curl_wssl_ctx_init(&ctx->wssl, cf, data, peer, alpns, + return Curl_wssl_ctx_init(&ctx->wssl, cf, data, ssl_peer, alpns, cb_setup, cb_user_data, ssl_user_data, session_reuse_cb); #else @@ -180,7 +182,7 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, NULL) == WOLFSSL_FAILURE)) result = CURLE_PEER_FAILED_VERIFICATION; else if(!peer->sni && - (wolfSSL_X509_check_ip_asc(cert, peer->dest->hostname, + (wolfSSL_X509_check_ip_asc(cert, peer->origin->hostname, 0) == WOLFSSL_FAILURE)) result = CURLE_PEER_FAILED_VERIFICATION; wolfSSL_X509_free(cert); diff --git a/lib/vquic/vquic-tls.h b/lib/vquic/vquic-tls.h index c461c1548b..e8d2418b06 100644 --- a/lib/vquic/vquic-tls.h +++ b/lib/vquic/vquic-tls.h @@ -66,13 +66,18 @@ typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf, struct Curl_ssl_session *scs, bool *do_early_data); +CURLcode Curl_vquic_tls_peer_init(struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, + struct ssl_peer *ssl_peer); + /** * Initialize the QUIC TLS instances based of the SSL configurations * for the connection filter, transfer and peer. * @param ctx the TLS context to initialize * @param cf the connection filter involved * @param data the transfer involved - * @param peer the peer to be connected to + * @param ssl_peer the SSL peer to be connected to * @param alpns the ALPN specifications to negotiate, may be NULL * @param cb_setup optional callback for early TLS config * @param cb_user_data user_data param for callback @@ -82,7 +87,7 @@ typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf, CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, struct Curl_cfilter *cf, struct Curl_easy *data, - struct ssl_peer *peer, + struct ssl_peer *ssl_peer, const struct alpn_spec *alpns, Curl_vquic_tls_ctx_setup *cb_setup, void *cb_user_data, diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index c0a0cbe3b7..2c076b2a70 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -42,6 +42,7 @@ #include "curlx/fopen.h" #include "cfilters.h" #include "vquic/cf-ngtcp2.h" +#include "vquic/cf-ngtcp2-cmn.h" #include "vquic/cf-ngtcp2-proxy.h" #include "vquic/cf-quiche.h" #include "multiif.h" @@ -760,35 +761,49 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, return CURLE_OK; } -CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at) +CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, + struct Curl_peer *origin, + struct Curl_peer *peer) { #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) - return Curl_cf_ngtcp2_insert_after(cf_at); + return Curl_cf_ngtcp2_insert_after(cf_at, origin, peer); +#elif defined(USE_QUICHE) + return Curl_cf_quiche_insert_after(cf_at, origin, peer); #else (void)cf_at; + (void)origin; + (void)peer; return CURLE_NOT_BUILT_IN; #endif } CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out) + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { - (void)transport_in; - (void)transport_out; - DEBUGASSERT(transport_out == TRNSPRT_QUIC); + (void)transport_peer; + (void)tunnel_transport; + (void)tunnel_peer; + DEBUGASSERT(transport_peer == TRNSPRT_QUIC); #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) - return Curl_cf_ngtcp2_create(pcf, data, conn, addr); + return Curl_cf_ngtcp2_create(pcf, data, origin, peer, conn, addr); #elif defined(USE_QUICHE) - return Curl_cf_quiche_create(pcf, data, conn, addr); + return Curl_cf_quiche_create(pcf, data, origin, peer, conn, addr); #else *pcf = NULL; (void)data; + (void)origin; + (void)peer; (void)conn; (void)addr; + (void)tunnel_peer; + (void)tunnel_transport; return CURLE_NOT_BUILT_IN; #endif } @@ -797,35 +812,49 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, CURLcode Curl_cf_h3_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - struct Curl_peer *dest, - bool udp_tunnel) + struct Curl_peer *origin, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) - return Curl_cf_ngtcp2_proxy_insert_after(cf_at, data, dest, udp_tunnel); + return Curl_cf_ngtcp2_proxy_insert_after(cf_at, data, origin, peer, + tunnel_peer, tunnel_transport); #else (void)cf_at; + (void)data; + (void)origin; + (void)peer; + (void)tunnel_peer; + (void)tunnel_transport; return CURLE_NOT_BUILT_IN; #endif } CURLcode Curl_cf_h3_proxy_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out) + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport) { - (void)transport_in; - (void)transport_out; - DEBUGASSERT(transport_out == TRNSPRT_QUIC); + DEBUGASSERT(transport_peer == TRNSPRT_QUIC); #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) - return Curl_cf_ngtcp2_proxy_create(pcf, data, conn, addr, - transport_in, transport_out); + return Curl_cf_ngtcp2_proxy_create(pcf, data, origin, peer, transport_peer, + conn, addr, + tunnel_peer, tunnel_transport); #else *pcf = NULL; (void)data; (void)conn; (void)addr; + (void)peer; + (void)transport_peer; + (void)tunnel_peer; + (void)tunnel_transport; return CURLE_NOT_BUILT_IN; #endif } diff --git a/lib/vquic/vquic.h b/lib/vquic/vquic.h index fe53803be3..5211a9b33a 100644 --- a/lib/vquic/vquic.h +++ b/lib/vquic/vquic.h @@ -39,14 +39,19 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, size_t scidlen, int *qlogfdp); -CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at); +CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, + struct Curl_peer *origin, + struct Curl_peer *peer); CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); extern struct Curl_cftype Curl_cft_http3; @@ -54,15 +59,20 @@ extern struct Curl_cftype Curl_cft_http3; CURLcode Curl_cf_h3_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, - struct Curl_peer *dest, - bool udp_tunnel); + struct Curl_peer *origin, + struct Curl_peer *peer, + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); CURLcode Curl_cf_h3_proxy_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out); + struct Curl_peer *tunnel_peer, + uint8_t tunnel_transport); extern struct Curl_cftype Curl_cft_h3_proxy; diff --git a/lib/vtls/apple.c b/lib/vtls/apple.c index 8ad77bd7fd..2e132f2977 100644 --- a/lib/vtls/apple.c +++ b/lib/vtls/apple.c @@ -102,7 +102,7 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, if(conn_config->verifyhost) { host_str = CFStringCreateWithCString(NULL, - peer->sni ? peer->sni : peer->dest->hostname, kCFStringEncodingUTF8); + peer->sni ? peer->sni : peer->origin->hostname, kCFStringEncodingUTF8); if(!host_str) { result = CURLE_OUT_OF_MEMORY; goto out; diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 6fda7590ce..1be2e381c3 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -1366,11 +1366,11 @@ static void gtls_msg_verify_result(struct Curl_easy *data, if(needs_verified) { failf(data, "SSL: certificate subject name (%s) does not match " "target hostname '%s'", certname, - peer->dest->user_hostname); + peer->origin->user_hostname); } else infof(data, " common name: %s (does not match '%s')", - certname, peer->dest->user_hostname); + certname, peer->origin->user_hostname); } else infof(data, " common name: %s (matched)", certname); @@ -1848,7 +1848,7 @@ CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, IP addresses) */ rc = (int)gnutls_x509_crt_check_hostname(x509_cert, peer->sni ? peer->sni : - peer->dest->hostname); + peer->origin->hostname); result = (!rc && config->verifyhost) ? CURLE_PEER_FAILED_VERIFICATION : CURLE_OK; gtls_msg_verify_result(data, peer, x509_cert, rc, config->verifyhost); diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index b750313084..e4bd8074ab 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -798,7 +798,7 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, char errorbuf[128]; infof(data, "mbedTLS: Connecting to %s:%d", - connssl->peer.dest->hostname, connssl->peer.dest->port); + connssl->peer.origin->hostname, connssl->peer.origin->port); mbedtls_ssl_config_init(&backend->config); ret = mbedtls_ssl_config_defaults(&backend->config, @@ -940,7 +940,7 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, if(mbedtls_ssl_set_hostname(&backend->ssl, connssl->peer.sni ? connssl->peer.sni : - connssl->peer.dest->hostname)) { + connssl->peer.origin->hostname)) { /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and the name to set in the SNI extension. Thus even if curl connects to a host specified as an IP address, this function must be used. */ diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 520ba95fa9..54ea089ae1 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -2042,19 +2042,19 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, CURLcode result = CURLE_OK; bool dNSName = FALSE; /* if a dNSName field exists in the cert */ bool iPAddress = FALSE; /* if an iPAddress field exists in the cert */ - size_t hostlen = strlen(peer->dest->hostname); + size_t hostlen = strlen(peer->origin->hostname); (void)conn; switch(peer->type) { case CURL_SSL_PEER_IPV4: - if(!curlx_inet_pton(AF_INET, peer->dest->hostname, &addr)) + if(!curlx_inet_pton(AF_INET, peer->origin->hostname, &addr)) return CURLE_PEER_FAILED_VERIFICATION; target = GEN_IPADD; addrlen = sizeof(struct in_addr); break; #ifdef USE_IPV6 case CURL_SSL_PEER_IPV6: - if(!curlx_inet_pton(AF_INET6, peer->dest->hostname, &addr)) + if(!curlx_inet_pton(AF_INET6, peer->origin->hostname, &addr)) return CURLE_PEER_FAILED_VERIFICATION; target = GEN_IPADD; addrlen = sizeof(struct in6_addr); @@ -2116,10 +2116,10 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, /* if this is not true, there was an embedded zero in the name string and we cannot match it. */ Curl_cert_hostcheck(altptr, altlen, - peer->dest->hostname, hostlen)) { + peer->origin->hostname, hostlen)) { matched = TRUE; infof(data, " subjectAltName: \"%s\" matches cert's \"%.*s\"", - peer->dest->user_hostname, (int)altlen, altptr); + peer->origin->user_hostname, (int)altlen, altptr); } break; @@ -2129,7 +2129,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, if((altlen == addrlen) && !memcmp(altptr, &addr, altlen)) { matched = TRUE; infof(data, " subjectAltName: \"%s\" matches cert's IP address!", - peer->dest->user_hostname); + peer->origin->user_hostname); } break; } @@ -2146,9 +2146,9 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, (peer->type == CURL_SSL_PEER_IPV4) ? "ipv4 address" : "ipv6 address"; infof(data, " subjectAltName does not match %s %s", tname, - peer->dest->user_hostname); + peer->origin->user_hostname); failf(data, "SSL: no alternative certificate subject name matches " - "target %s '%s'", tname, peer->dest->user_hostname); + "target %s '%s'", tname, peer->origin->user_hostname); result = CURLE_PEER_FAILED_VERIFICATION; } else { @@ -2208,9 +2208,9 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, result = CURLE_PEER_FAILED_VERIFICATION; } else if(!Curl_cert_hostcheck((const char *)cn, cnlen, - peer->dest->hostname, hostlen)) { + peer->origin->hostname, hostlen)) { failf(data, "SSL: certificate subject name '%s' does not match " - "target hostname '%s'", cn, peer->dest->user_hostname); + "target hostname '%s'", cn, peer->origin->user_hostname); result = CURLE_PEER_FAILED_VERIFICATION; } else { @@ -3534,9 +3534,9 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, #else if(trying_ech_now && outername) { infof(data, "ECH: inner: '%s', outer: '%s'", - peer->dest->hostname ? peer->dest->hostname : "NULL", outername); + peer->origin->hostname ? peer->origin->hostname : "NULL", outername); result = SSL_ech_set1_server_names(octx->ssl, - peer->dest->hostname, outername, + peer->origin->hostname, outername, 0 /* do send outer */); if(result != 1) { infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", result); @@ -4010,19 +4010,12 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, { struct ssl_connect_data *connssl = cf->ctx; struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; - char tls_id[80]; BIO *bio; CURLcode result; DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); DEBUGASSERT(octx); - - if(!connssl->peer.dest) { - Curl_ossl_version(tls_id, sizeof(tls_id)); - result = Curl_ssl_peer_init(&connssl->peer, cf, tls_id, TRNSPRT_TCP); - if(result) - return result; - } + DEBUGASSERT(connssl->peer.origin); result = Curl_ossl_ctx_init(octx, cf, data, &connssl->peer, connssl->alpn, NULL, NULL, @@ -4277,7 +4270,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, curlx_strerror(sockerr, extramsg, sizeof(extramsg)); failf(data, OSSL_PACKAGE " SSL_connect: %s in connection to %s:%d ", extramsg[0] ? extramsg : SSL_ERROR_to_str(detail), - connssl->peer.dest->hostname, connssl->peer.dest->port); + connssl->peer.origin->hostname, connssl->peer.origin->port); } return result; @@ -4324,7 +4317,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); if(!conn_config->verifypeer && !conn_config->verifyhost && - inner && !strcmp(inner, connssl->peer.dest->hostname)) { + inner && !strcmp(inner, connssl->peer.origin->hostname)) { VERBOSE(status = "bad name (tolerated without peer verification)"); rv = SSL_ECH_STATUS_SUCCESS; } diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index d69ec03f8f..e1a3eacd1c 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -1102,7 +1102,7 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, DEBUGASSERT(!rconn); rr = rustls_client_connection_new(backend->config, - connssl->peer.dest->hostname, + connssl->peer.origin->hostname, &rconn); if(rr != RUSTLS_RESULT_OK) { rustls_failf(data, rr, "rustls_client_connection_new"); diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index c0b46c58c7..8c714faa5e 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -849,7 +849,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, DEBUGASSERT(backend); DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 1/3)", - connssl->peer.dest->hostname, connssl->peer.dest->port)); + connssl->peer.origin->hostname, connssl->peer.origin->port)); #ifdef HAS_ALPN_SCHANNEL backend->use_alpn = connssl->alpn && s_win_has_alpn; @@ -902,7 +902,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, /* A hostname associated with the credential is needed by InitializeSecurityContext for SNI and other reasons. */ snihost = connssl->peer.sni ? - connssl->peer.sni : connssl->peer.dest->hostname; + connssl->peer.sni : connssl->peer.origin->hostname; backend->cred->sni_hostname = curlx_convert_UTF8_to_tchar(snihost); if(!backend->cred->sni_hostname) return CURLE_OUT_OF_MEMORY; @@ -1245,7 +1245,7 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, connssl->io_need = CURL_SSL_IO_NEED_NONE; DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 2/3)", - connssl->peer.dest->hostname, connssl->peer.dest->port)); + connssl->peer.origin->hostname, connssl->peer.origin->port)); if(!backend->cred || !backend->ctxt) return CURLE_SSL_CONNECT_ERROR; @@ -1597,7 +1597,7 @@ static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, DEBUGASSERT(backend); DEBUGF(infof(data, "schannel: SSL/TLS connection with %s port %d (step 3/3)", - connssl->peer.dest->hostname, connssl->peer.dest->port)); + connssl->peer.origin->hostname, connssl->peer.origin->port)); if(!backend->cred) return CURLE_SSL_CONNECT_ERROR; @@ -2435,7 +2435,7 @@ static CURLcode schannel_shutdown(struct Curl_cfilter *cf, *done = FALSE; if(backend->ctxt) { infof(data, "schannel: shutting down SSL/TLS connection with %s port %d", - connssl->peer.dest->hostname, connssl->peer.dest->port); + connssl->peer.origin->hostname, connssl->peer.origin->port); } if(!backend->ctxt || cf->shutdown) { diff --git a/lib/vtls/schannel_verify.c b/lib/vtls/schannel_verify.c index 38be1dcdc0..cd00287ff2 100644 --- a/lib/vtls/schannel_verify.c +++ b/lib/vtls/schannel_verify.c @@ -480,7 +480,7 @@ CURLcode Curl_verify_host(struct Curl_cfilter *cf, struct Curl_easy *data) SECURITY_STATUS sspi_status; TCHAR *cert_hostname_buff = NULL; size_t cert_hostname_buff_index = 0; - const char *conn_hostname = connssl->peer.dest->hostname; + const char *conn_hostname = connssl->peer.origin->hostname; size_t hostlen = strlen(conn_hostname); DWORD len = 0; DWORD actual_len = 0; diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 78a956a166..913c39083c 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -869,7 +869,8 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, void Curl_ssl_peer_cleanup(struct ssl_peer *peer) { - Curl_peer_unlink(&peer->dest); + Curl_peer_unlink(&peer->origin); + Curl_peer_unlink(&peer->peer); curlx_safefree(peer->sni); curlx_safefree(peer->scache_key); peer->transport = TRNSPRT_NONE; @@ -908,62 +909,49 @@ static ssl_peer_type get_peer_type(const char *hostname) return CURL_SSL_PEER_DNS; } -CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, - struct Curl_cfilter *cf, +CURLcode Curl_ssl_peer_init(struct ssl_peer *ssl_peer, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, const char *tls_id, uint8_t transport) { - struct Curl_peer *dest = NULL; CURLcode result = CURLE_OUT_OF_MEMORY; /* We expect a clean struct, e.g. called only ONCE */ - DEBUGASSERT(peer); - DEBUGASSERT(!peer->dest); - DEBUGASSERT(!peer->sni); - /* We need the hostname for SNI negotiation. Once handshaked, this remains - * the SNI hostname for the TLS connection. When the connection is reused, - * the settings in cf->conn might change. We keep a copy of the hostname we - * use for SNI. - */ - peer->transport = transport; -#ifndef CURL_DISABLE_PROXY - if(Curl_ssl_cf_is_proxy(cf)) { - dest = cf->conn->http_proxy.peer; - } - else -#endif - { - dest = cf->conn->origin; + if(!ssl_peer || !origin) { + DEBUGASSERT(0); + return CURLE_FAILED_INIT; } + DEBUGASSERT(!ssl_peer->origin); + DEBUGASSERT(!ssl_peer->peer); + DEBUGASSERT(!ssl_peer->sni); + ssl_peer->transport = transport; - /* hostname MUST exist and not be empty */ - if(!dest) { - result = CURLE_FAILED_INIT; - goto out; - } - - Curl_peer_link(&peer->dest, dest); - peer->type = get_peer_type(dest->hostname); - if(peer->type == CURL_SSL_PEER_DNS) { + Curl_peer_link(&ssl_peer->origin, origin); + Curl_peer_link(&ssl_peer->peer, peer); + ssl_peer->type = get_peer_type(origin->hostname); + if(ssl_peer->type == CURL_SSL_PEER_DNS) { /* not an IP address, normalize according to RCC 6066 ch. 3, * max len of SNI is 2^16-1, no trailing dot */ - size_t len = strlen(dest->hostname); - if(len && (dest->hostname[len - 1] == '.')) + size_t len = strlen(origin->hostname); + if(len && (origin->hostname[len - 1] == '.')) len--; if(len < USHRT_MAX) { - peer->sni = curlx_calloc(1, len + 1); - if(!peer->sni) + ssl_peer->sni = curlx_calloc(1, len + 1); + if(!ssl_peer->sni) goto out; - Curl_strntolower(peer->sni, dest->hostname, len); - peer->sni[len] = 0; + Curl_strntolower(ssl_peer->sni, origin->hostname, len); + ssl_peer->sni[len] = 0; } } - result = Curl_ssl_peer_key_make(cf, peer, tls_id, &peer->scache_key); + result = Curl_ssl_peer_key_make(ssl_peer, sslc, tls_id, + &ssl_peer->scache_key); out: if(result) - Curl_ssl_peer_cleanup(peer); + Curl_ssl_peer_cleanup(ssl_peer); return result; } @@ -991,7 +979,7 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, return CURLE_OK; } - if(!cf->next) { + if(!cf->next || !connssl->peer.origin) { *done = FALSE; return CURLE_FAILED_INIT; } @@ -1016,14 +1004,6 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, connssl->prefs_checked = TRUE; } - if(!connssl->peer.dest) { - char tls_id[80]; - connssl->ssl_impl->version(tls_id, sizeof(tls_id) - 1); - result = Curl_ssl_peer_init(&connssl->peer, cf, tls_id, TRNSPRT_TCP); - if(result) - goto out; - } - result = connssl->ssl_impl->do_connect(cf, data, done); if(!result && *done) { @@ -1406,28 +1386,53 @@ out: return result; } +static CURLcode cf_ssl_peer_init(struct Curl_cfilter *cf, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc) +{ + struct ssl_connect_data *connssl = cf->ctx; + char tls_id[80]; + connssl->ssl_impl->version(tls_id, sizeof(tls_id) - 1); + return Curl_ssl_peer_init(&connssl->peer, origin, peer, sslc, + tls_id, TRNSPRT_TCP); +} + CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, + struct Curl_peer *origin, struct connectdata *conn, int sockindex) { struct Curl_cfilter *cf; + struct Curl_peer *peer = (sockindex == SECONDARYSOCKET) ? + conn->via_peer2 : conn->via_peer; CURLcode result; result = cf_ssl_create(&cf, data, conn); + if(!result) + result = cf_ssl_peer_init(cf, origin, peer, &conn->ssl_config); if(!result) Curl_conn_cf_add(data, conn, sockindex, cf); + else if(cf) + Curl_conn_cf_discard_chain(&cf, data); return result; } CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data) + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer) { struct Curl_cfilter *cf; CURLcode result; result = cf_ssl_create(&cf, data, cf_at->conn); + if(!result) + result = cf_ssl_peer_init(cf, origin, peer, &cf_at->conn->ssl_config); if(!result) Curl_conn_cf_insert_after(cf_at, cf); + else if(cf) + Curl_conn_cf_discard_chain(&cf, data); return result; } @@ -1467,14 +1472,19 @@ out: } CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data) + struct Curl_easy *data, + struct Curl_peer *peer) { struct Curl_cfilter *cf; CURLcode result; result = cf_ssl_proxy_create(&cf, data, cf_at->conn); + if(!result) + result = cf_ssl_peer_init(cf, peer, NULL, &cf_at->conn->proxy_ssl_config); if(!result) Curl_conn_cf_insert_after(cf_at, cf); + else if(cf) + Curl_conn_cf_discard_chain(&cf, data); return result; } diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h index f0825c37ed..4bf99ad30c 100644 --- a/lib/vtls/vtls.h +++ b/lib/vtls/vtls.h @@ -89,7 +89,8 @@ typedef enum { } ssl_peer_type; struct ssl_peer { - struct Curl_peer *dest; + struct Curl_peer *origin; /* the authority we talk to */ + struct Curl_peer *peer; /* the machine we are connected to */ char *sni; /* SNI version of hostname or NULL if not usable */ char *scache_key; /* for lookups in session cache */ ssl_peer_type type; /* type of the peer information */ @@ -106,8 +107,10 @@ curl_sslbackend Curl_ssl_backend(void); /** * Init SSL peer information for filter. Can be called repeatedly. */ -CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, - struct Curl_cfilter *cf, +CURLcode Curl_ssl_peer_init(struct ssl_peer *ssl_peer, + struct Curl_peer *origin, + struct Curl_peer *peer, + struct ssl_primary_config *sslc, const char *tls_id, uint8_t transport); /** @@ -174,18 +177,22 @@ CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, #define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */ CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, + struct Curl_peer *origin, struct connectdata *conn, int sockindex); CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data); + struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer); CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, int sockindex, bool send_shutdown); #ifndef CURL_DISABLE_PROXY CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, - struct Curl_easy *data); + struct Curl_easy *data, + struct Curl_peer *peer); #endif /* !CURL_DISABLE_PROXY */ /** @@ -225,7 +232,7 @@ extern struct Curl_cftype Curl_cft_ssl_proxy; #define Curl_ssl_random(x, y, z) ((void)(x), CURLE_NOT_BUILT_IN) #define Curl_ssl_cert_status_request() FALSE #define Curl_ssl_supports(a, b) FALSE -#define Curl_ssl_cfilter_add(a, b, c) CURLE_NOT_BUILT_IN +#define Curl_ssl_cfilter_add(a, b, c, d) CURLE_NOT_BUILT_IN #define Curl_ssl_cfilter_remove(a, b, c) CURLE_OK #define Curl_ssl_cf_get_config(a, b) NULL #define Curl_ssl_cf_get_primary_config(a) NULL diff --git a/lib/vtls/vtls_scache.c b/lib/vtls/vtls_scache.c index fe30091bfb..24a568fbf4 100644 --- a/lib/vtls/vtls_scache.c +++ b/lib/vtls/vtls_scache.c @@ -177,11 +177,59 @@ static bool cf_ssl_peer_key_is_global(const char *peer_key) (peer_key[len - 2] == ':'); } -CURLcode Curl_ssl_peer_key_build(struct ssl_primary_config *ssl, - const struct ssl_peer *peer, - const struct Curl_peer *via_peer, - const char *tls_id, - char **ppeer_key) +static CURLcode ssl_peer_key_add_transport(struct dynbuf *buf, + uint8_t transport) +{ + switch(transport) { + case TRNSPRT_TCP: + return CURLE_OK; + case TRNSPRT_UDP: + return curlx_dyn_add(buf, ":UDP"); + case TRNSPRT_QUIC: + return curlx_dyn_add(buf, ":QUIC"); + case TRNSPRT_UNIX: + return curlx_dyn_add(buf, ":UNIX"); + default: + return curlx_dyn_addf(buf, ":TRNSPRT-%d", transport); + } +} + +static CURLcode ssl_peer_key_add_vrfy(struct dynbuf *buf, + struct ssl_primary_config *ssl, + const struct ssl_peer *peer) +{ + CURLcode result; + + if(!ssl->verifypeer) { + result = curlx_dyn_add(buf, ":NO-VRFY-PEER"); + if(result) + return result; + } + if(!ssl->verifyhost) { + result = curlx_dyn_add(buf, ":NO-VRFY-HOST"); + if(result) + return result; + } + if(ssl->verifystatus) { + result = curlx_dyn_add(buf, ":VRFY-STATUS"); + if(result) + return result; + } + if((!ssl->verifypeer || !ssl->verifyhost) && + peer->peer && !Curl_peer_equal(peer->origin, peer->peer)) { + result = curlx_dyn_addf(buf, ":CHOST-%s:CPORT-%u", + peer->peer->hostname, + peer->peer->port); + if(result) + return result; + } + return CURLE_OK; +} + +static CURLcode ssl_peer_key_build(struct ssl_primary_config *ssl, + const struct ssl_peer *peer, + const char *tls_id, + char **ppeer_key) { struct dynbuf buf; size_t key_len; @@ -192,54 +240,15 @@ CURLcode Curl_ssl_peer_key_build(struct ssl_primary_config *ssl, curlx_dyn_init(&buf, 10 * 1024); result = curlx_dyn_addf(&buf, "%s:%d", - peer->dest->hostname, peer->dest->port); + peer->origin->hostname, peer->origin->port); if(result) goto out; - - switch(peer->transport) { - case TRNSPRT_TCP: - break; - case TRNSPRT_UDP: - result = curlx_dyn_add(&buf, ":UDP"); - break; - case TRNSPRT_QUIC: - result = curlx_dyn_add(&buf, ":QUIC"); - break; - case TRNSPRT_UNIX: - result = curlx_dyn_add(&buf, ":UNIX"); - break; - default: - result = curlx_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport); - break; - } + result = ssl_peer_key_add_transport(&buf, peer->transport); + if(result) + goto out; + result = ssl_peer_key_add_vrfy(&buf, ssl, peer); if(result) goto out; - - if(!ssl->verifypeer) { - result = curlx_dyn_add(&buf, ":NO-VRFY-PEER"); - if(result) - goto out; - } - if(!ssl->verifyhost) { - result = curlx_dyn_add(&buf, ":NO-VRFY-HOST"); - if(result) - goto out; - } - if(ssl->verifystatus) { - result = curlx_dyn_add(&buf, ":VRFY-STATUS"); - if(result) - goto out; - } - if(!ssl->verifypeer || !ssl->verifyhost) { - if(via_peer) { - result = curlx_dyn_addf(&buf, ":CHOST-%s:CPORT-%u", - via_peer->hostname, - via_peer->port); - if(result) - goto out; - } - } - if(ssl->version || ssl->version_max) { result = curlx_dyn_addf(&buf, ":TLSVER-%d-%u", ssl->version, (ssl->version_max >> 16)); @@ -342,14 +351,12 @@ out: return result; } -CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, - const struct ssl_peer *peer, +CURLcode Curl_ssl_peer_key_make(const struct ssl_peer *peer, + struct ssl_primary_config *sslc, const char *tls_id, char **ppeer_key) { - struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf); - return Curl_ssl_peer_key_build(ssl, peer, cf->conn->via_peer, tls_id, - ppeer_key); + return ssl_peer_key_build(sslc, peer, tls_id, ppeer_key); } struct Curl_ssl_scache { diff --git a/lib/vtls/vtls_scache.h b/lib/vtls/vtls_scache.h index bfb0677e84..effb1d8f96 100644 --- a/lib/vtls/vtls_scache.h +++ b/lib/vtls/vtls_scache.h @@ -54,34 +54,18 @@ void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache); * connection to the peer. * If the filter is a TLS proxy filter, it uses the proxy relevant * information. - * @param cf the connection filter wanting to use it * @param peer the peer the filter wants to talk to + * @param sslc the relevant ssl configuration * @param tls_id identifier of TLS implementation for sessions. Should * include full version if session data from other versions * is to be avoided. * @param ppeer_key on successful return, the key generated */ -CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf, - const struct ssl_peer *peer, +CURLcode Curl_ssl_peer_key_make(const struct ssl_peer *peer, + struct ssl_primary_config *sslc, const char *tls_id, char **ppeer_key); -/** - * Like Curl_ssl_peer_key_make() but takes the primary config and peer - * descriptors directly, without requiring a Curl_cfilter. Exposed for - * unit testing. - * @param ssl the primary SSL config to key on - * @param peer the peer the filter wants to talk to - * @param via_peer the connecting-through peer, or NULL - * @param tls_id identifier of TLS implementation for sessions - * @param ppeer_key on successful return, the key generated - */ -CURLcode Curl_ssl_peer_key_build(struct ssl_primary_config *ssl, - const struct ssl_peer *peer, - const struct Curl_peer *via_peer, - const char *tls_id, - char **ppeer_key); - /* Return if there is a session cache shall be used. * An SSL session might not be configured or not available for * "connect-only" transfers. diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index c7d86a8101..bed18998b3 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -1763,9 +1763,9 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) failf(data, "unable to get peer certificate"); return CURLE_PEER_FAILED_VERIFICATION; } - ret = wolfSSL_X509_check_ip_asc(cert, connssl->peer.dest->hostname, 0); + ret = wolfSSL_X509_check_ip_asc(cert, connssl->peer.origin->hostname, 0); CURL_TRC_CF(data, cf, "check peer certificate for IP match on %s -> %d", - connssl->peer.dest->hostname, ret); + connssl->peer.origin->hostname, ret); if(ret != WOLFSSL_SUCCESS) detail = DOMAIN_NAME_MISMATCH; wolfSSL_X509_free(cert); @@ -1788,7 +1788,7 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) * This enables the override of both mismatching SubjectAltNames * as also mismatching CN fields */ failf(data, " subject alt name(s) or common name do not match \"%s\"", - connssl->peer.dest->hostname); + connssl->peer.origin->hostname); return CURLE_PEER_FAILED_VERIFICATION; } else if(ASN_NO_SIGNER_E == detail) { diff --git a/tests/http/test_06_eyeballs.py b/tests/http/test_06_eyeballs.py index fb9df11d2f..a423524fa5 100644 --- a/tests/http/test_06_eyeballs.py +++ b/tests/http/test_06_eyeballs.py @@ -214,3 +214,16 @@ class TestEyeballs: r.check_exit_code(0) r.check_response(count=1, http_status=200) assert r.stats[0]['http_version'] == '2' + + # h3 download using --connect-to IPv6 address + @pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support") + @pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), reason="no IPv6") + def test_06_25_h3_connect_to(self, env: Env, httpd, nghttpx): + curl = CurlClient(env=env, force_resolv=False) + urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json' + r = curl.http_download(urls=[urln], extra_args=[ + '--http3-only', '--connect-to', + f'{env.authority_for(env.domain1, "h3")}:[::1]:{env.https_port}' + ]) + r.check_response(count=1, http_status=200) + assert r.stats[0]['http_version'] == '3' diff --git a/tests/unit/unit2600.c b/tests/unit/unit2600.c index fc4d8b7d2c..e0eecf5b13 100644 --- a/tests/unit/unit2600.c +++ b/tests/unit/unit2600.c @@ -113,8 +113,7 @@ static int test_idx; struct cf_test_ctx { int idx; int ai_family; - uint8_t transport_in; - uint8_t transport_out; + uint8_t transport_peer; char id[16]; struct curltime started; timediff_t fail_delay_ms; @@ -166,10 +165,13 @@ static CURLcode cf_test_adjust_pollset(struct Curl_cfilter *cf, static CURLcode cf_test_create(struct Curl_cfilter **pcf, struct Curl_easy *data, + struct Curl_peer *origin, + struct Curl_peer *peer, + uint8_t transport_peer, struct connectdata *conn, struct Curl_sockaddr_ex *addr, - uint8_t transport_in, - uint8_t transport_out) + struct Curl_peer *tunnel_peer, + uint8_t transport_above) { static const struct Curl_cftype cft_test = { "TEST", @@ -194,7 +196,11 @@ static CURLcode cf_test_create(struct Curl_cfilter **pcf, CURLcode result; (void)data; + (void)origin; + (void)peer; (void)conn; + (void)tunnel_peer; + (void)transport_above; ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; @@ -202,8 +208,7 @@ static CURLcode cf_test_create(struct Curl_cfilter **pcf, } ctx->idx = test_idx++; ctx->ai_family = addr->family; - ctx->transport_in = transport_in; - ctx->transport_out = transport_out; + ctx->transport_peer = transport_peer; ctx->started = curlx_now(); current_tr->ongoing++; if(current_tr->ongoing > current_tr->max_concurrent) diff --git a/tests/unit/unit3304.c b/tests/unit/unit3304.c index 5573be39cc..bb2bd77b86 100644 --- a/tests/unit/unit3304.c +++ b/tests/unit/unit3304.c @@ -43,7 +43,7 @@ static CURLcode test_unit3304(const char *arg) UNITTEST_BEGIN_SIMPLE #ifdef USE_SSL - struct Curl_peer dest; + struct Curl_peer origin; struct ssl_peer peer; struct ssl_primary_config ssl; char *key1 = NULL; @@ -60,12 +60,12 @@ static CURLcode test_unit3304(const char *arg) static char lc_ctype[] = "pem"; static char lc_ktype[] = "pem"; - memset(&dest, 0, sizeof(dest)); - dest.hostname = base_hostname; - dest.port = 443; + memset(&origin, 0, sizeof(origin)); + origin.hostname = base_hostname; + origin.port = 443; memset(&peer, 0, sizeof(peer)); - peer.dest = &dest; + peer.origin = &origin; peer.transport = TRNSPRT_TCP; memset(&ssl, 0, sizeof(ssl)); @@ -78,9 +78,9 @@ static CURLcode test_unit3304(const char *arg) ssl.key_type = base_ktype; /* Baseline: same config produces same key. */ - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key1), "peer key build failed"); - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key2), "peer key build failed"); fail_unless(key1 && key2 && !strcmp(key1, key2), "identical config should produce identical peer key"); @@ -89,10 +89,10 @@ static CURLcode test_unit3304(const char *arg) /* key_passwd is NOT in the peer key: lookup uses timing-safe comparison * via cf_ssl_scache_match_auth(), same as SRP credentials. */ - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key1), "peer key build failed"); ssl.key_passwd = NULL; - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key2), "peer key build failed"); fail_unless(key1 && key2 && !strcmp(key1, key2), "key_passwd must not affect the peer key"); @@ -101,10 +101,10 @@ static CURLcode test_unit3304(const char *arg) ssl.key_passwd = base_passwd; /* Different key path must produce a different peer key. */ - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key1), "peer key build failed"); ssl.key = alt_key; - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key2), "peer key build failed"); fail_unless(key1 && key2 && strcmp(key1, key2), "different key must produce different peer key"); @@ -113,10 +113,10 @@ static CURLcode test_unit3304(const char *arg) ssl.key = base_key; /* Different key_type must produce a different peer key. */ - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key1), "peer key build failed"); ssl.key_type = alt_ktype; - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key2), "peer key build failed"); fail_unless(key1 && key2 && strcmp(key1, key2), "different key_type must produce different peer key"); @@ -125,10 +125,10 @@ static CURLcode test_unit3304(const char *arg) ssl.key_type = base_ktype; /* Different cert_type must produce a different peer key. */ - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key1), "peer key build failed"); ssl.cert_type = alt_ctype; - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key2), "peer key build failed"); fail_unless(key1 && key2 && strcmp(key1, key2), "different cert_type must produce different peer key"); @@ -138,10 +138,10 @@ static CURLcode test_unit3304(const char *arg) /* cert_type is case-insensitive: "PEM" and "pem" must produce the * same peer key, consistent with the conn-reuse comparison. */ - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key1), "peer key build failed"); ssl.cert_type = lc_ctype; - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key2), "peer key build failed"); fail_unless(key1 && key2 && !strcmp(key1, key2), "cert_type case must not affect peer key"); @@ -151,10 +151,10 @@ static CURLcode test_unit3304(const char *arg) /* key_type is case-insensitive: "PEM" and "pem" must produce the * same peer key. */ - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key1), "peer key build failed"); ssl.key_type = lc_ktype; - fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2), + fail_unless(!Curl_ssl_peer_key_make(&peer, &ssl, "test", &key2), "peer key build failed"); fail_unless(key1 && key2 && !strcmp(key1, key2), "key_type case must not affect peer key");