← Commit list

f924489b25

Stefan Eissing
Stefan Eissing
2026-06-10
SUCCESS Duration: 255s

Subject

ngtcp2: share common functionality

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

(log yok)

Commit Diff GitHub →

lib/Makefile.inc+2 -0
@@ -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 \
lib/cf-ip-happy.c+101 -81
@@ -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;
lib/cf-ip-happy.h+18 -19
@@ -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;
lib/cf-socket.c+49 -23
@@ -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;
lib/cf-socket.h+15 -6
@@ -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.
lib/connect.c+54 -15
@@ -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)
{
lib/connect.h+9 -0
@@ -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,
lib/ftp.c+7 -3
@@ -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;
lib/http_proxy.c+25 -21
@@ -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)
lib/http_proxy.h+3 -2
@@ -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;
lib/imap.c+2 -1
@@ -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 */
lib/multi.c+13 -7
@@ -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)
lib/openldap.c+2 -1
@@ -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();
lib/pop3.c+2 -1
@@ -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 */
lib/smtp.c+2 -1
@@ -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 */
lib/urldata.h+2 -0
@@ -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];
lib/vquic/cf-ngtcp2-cmn.c+1965 -0
@@ -0,0 +1,1965 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <[email protected]>, 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 <ngtcp2/ngtcp2.h>
+
+#ifdef USE_OPENSSL
+#include <openssl/err.h>
+#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL)
+#include <ngtcp2/ngtcp2_crypto_boringssl.h>
+#elif defined(OPENSSL_QUIC_API2)
+#include <ngtcp2/ngtcp2_crypto_ossl.h>
+#else
+#include <ngtcp2/ngtcp2_crypto_quictls.h>
+#endif
+#include "vtls/openssl.h"
+#elif defined(USE_GNUTLS)
+#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+#include "vtls/gtls.h"
+#elif defined(USE_WOLFSSL)
+#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
+#include "vtls/wolfssl.h"
+#endif
+
+#include <nghttp3/nghttp3.h>
+
+#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 <v1.22.0 */
+static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid,
+ uint8_t *token, size_t cidlen,
+ void *user_data)
+{
+ CURLcode result;
+ (void)tconn;
+ (void)user_data;
+
+ result = Curl_rand(NULL, cid->data, cidlen);
+ if(result)
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ cid->datalen = cidlen;
+
+ result = Curl_rand(NULL, token, NGTCP2_STATELESS_RESET_TOKENLEN);
+ if(result)
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+
+ return 0;
+}
+
+#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 <v1.22.0 */
+ NULL, /* remove_connection_id */
+ ngtcp2_crypto_update_key_cb, /* update_key */
+ NULL, /* path_validation */
+ NULL, /* select_preferred_addr */
+ cb_stream_reset,
+ NULL, /* extend_max_remote_streams_bidi */
+ NULL, /* extend_max_remote_streams_uni */
+ cb_extend_max_stream_data,
+ NULL, /* dcid_status */
+ NULL, /* handshake_confirmed */
+ NULL, /* recv_new_token */
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ NULL, /* recv_datagram */
+ NULL, /* ack_datagram */
+ NULL, /* lost_datagram */
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ cb_stream_stop_sending,
+ NULL, /* version_negotiation */
+ cb_recv_rx_key,
+ NULL, /* recv_tx_key */
+ NULL, /* early_data_rejected */
+#ifdef NGTCP2_CALLBACKS_V2 /* ngtcp2 v1.14.0+ */
+ NULL, /* begin_path_validation */
+#endif
+#ifdef NGTCP2_CALLBACKS_V3 /* ngtcp2 v1.22.0+ */
+ NULL, /* recv_stateless_reset2 */
+ cb_get_new_connection_id2, /* get_new_connection_id2 */
+ NULL, /* dcid_status2 */
+ ngtcp2_crypto_get_path_challenge_data2_cb, /* get_path_challenge_data2 */
+#endif
+};
+
+#if defined(_MSC_VER) && defined(_DLL)
+#pragma warning(pop)
+#endif
+
+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
+}
+
+#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->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 */
lib/vquic/cf-ngtcp2-cmn.h+239 -0
@@ -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, <[email protected]>, 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 <ngtcp2/ngtcp2.h>
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#ifdef USE_OPENSSL
+#include <openssl/err.h>
+#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL)
+#include <ngtcp2/ngtcp2_crypto_boringssl.h>
+#elif defined(OPENSSL_QUIC_API2)
+#include <ngtcp2/ngtcp2_crypto_ossl.h>
+#else
+#include <ngtcp2/ngtcp2_crypto_quictls.h>
+#endif
+#include "vtls/openssl.h"
+#elif defined(USE_GNUTLS)
+#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+#include "vtls/gtls.h"
+#elif defined(USE_WOLFSSL)
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/quic.h>
+#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
+#include "vtls/wolfssl.h"
+#endif
+
+#ifdef HAVE_NETINET_UDP_H
+#include <netinet/udp.h>
+#endif
+
+#include <nghttp3/nghttp3.h>
+
+#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 */
lib/vquic/cf-ngtcp2-proxy.c+403 -2567
@@ -27,46 +27,18 @@
defined(USE_PROXY_HTTP3) && defined(USE_NGHTTP3) && \
defined(USE_NGTCP2) && defined(USE_OPENSSL)
-#include <ngtcp2/ngtcp2.h>
-#include <ngtcp2/ngtcp2_crypto.h>
-
-#ifdef USE_OPENSSL
-#include <openssl/err.h>
-#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL)
-#include <ngtcp2/ngtcp2_crypto_boringssl.h>
-#elif defined(OPENSSL_QUIC_API2)
-#include <ngtcp2/ngtcp2_crypto_ossl.h>
-#else
-#include <ngtcp2/ngtcp2_crypto_quictls.h>
-#endif
-#include "vtls/openssl.h"
-#endif
-
-#include <nghttp3/nghttp3.h>
-
#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 */
-};
-
-#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)
-{
- 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 CURLcode cf_ngtcp2_proxy_h3_init(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct cf_ngtcp2_ctx *ctx);
-static void cf_h3_proxy_ctx_clear(struct cf_h3_proxy_ctx *ctx)
+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(&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;
+ 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_user_data;
+ if(!data)
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
- if(!ctx)
+ 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);
- }
-
- /* 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;
+ (void)stream_user_data;
+ if(!data)
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
- if(!ctx)
+ 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)
-{
- 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)
+static CURLcode cf_ngtcp2_proxy_h3_init(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct cf_ngtcp2_ctx *ctx)
{
- 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,975 +650,25 @@ 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)
+static ssize_t cf_h3_proxy_recv_closed_stream(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct h3_stream_ctx *stream,
+ CURLcode *err)
{
- 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);
- }
+ ssize_t nread = -1;
+ *err = CURLE_OK;
- /* 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 <v1.22.0 */
-static int cb_ngtcp2_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid,
- uint8_t *token, size_t cidlen,
- void *user_data)
-{
- CURLcode result;
- (void)tconn;
- (void)user_data;
-
- result = Curl_rand(NULL, cid->data, cidlen);
- if(result)
- return NGTCP2_ERR_CALLBACK_FAILURE;
- cid->datalen = cidlen;
-
- result = Curl_rand(NULL, token, NGTCP2_STATELESS_RESET_TOKENLEN);
- if(result)
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- return 0;
-}
-
-#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 <v1.22.0 */
- NULL, /* remove_connection_id */
- ngtcp2_crypto_update_key_cb,
- NULL, /* path_validation */
- NULL, /* select_preferred_addr */
- cb_ngtcp2_stream_reset,
- NULL, /* extend_max_remote_streams_bidi */
- NULL, /* extend_max_remote_streams_uni */
- cb_ngtcp2_extend_max_stream_data,
- NULL, /* dcid_status */
- NULL, /* handshake_confirmed */
- NULL, /* recv_new_token */
- ngtcp2_crypto_delete_crypto_aead_ctx_cb,
- ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
- NULL, /* recv_datagram */
- NULL, /* ack_datagram */
- NULL, /* lost_datagram */
- ngtcp2_crypto_get_path_challenge_data_cb,
- cb_ngtcp2_stream_stop_sending,
- NULL, /* version_negotiation */
- cb_ngtcp2_recv_rx_key, /* recv_rx_key */
- NULL, /* recv_tx_key */
- NULL, /* early_data_rejected */
-#ifdef NGTCP2_CALLBACKS_V2 /* ngtcp2 v1.14.0+ */
- NULL, /* begin_path_validation */
-#endif
-#ifdef NGTCP2_CALLBACKS_V3 /* ngtcp2 v1.22.0+ */
- NULL, /* recv_stateless_reset2 */
- cb_ngtcp2_get_new_connection_id2, /* get_new_connection_id2 */
- NULL, /* dcid_status2 */
- ngtcp2_crypto_get_path_challenge_data2_cb, /* get_path_challenge_data2 */
-#endif
-};
-
-#if defined(_MSC_VER) && defined(_DLL)
-#pragma warning(pop)
-#endif
-
-static CURLcode cf_ngtcp2_recv_pkts_proxy(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 proxy_pkt_io_ctx *pktx = userp;
- struct cf_h3_proxy_ctx *proxy_ctx = pktx->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)
-{
- ssize_t nread = -1;
- *err = CURLE_OK;
-
- if(stream->reset) {
- if(stream->error3 == CURL_H3_ERR_REQUEST_REJECTED) {
- infof(data, "HTTP/3 stream %" PRId64 " refused by server, try again "
- "on a new connection", stream->id);
- connclose(cf->conn, "REFUSED_STREAM");
- data->state.refused_stream = TRUE;
- *err = CURLE_RECV_ERROR;
- goto out;
+ if(stream->reset) {
+ if(stream->error3 == CURL_H3_ERR_REQUEST_REJECTED) {
+ infof(data, "HTTP/3 stream %" PRId64 " refused by server, try again "
+ "on a new connection", stream->id);
+ connclose(cf->conn, "REFUSED_STREAM");
+ data->state.refused_stream = TRUE;
+ *err = CURLE_RECV_ERROR;
+ goto out;
}
else if(stream->resp_hds_complete && data->req.no_body) {
CURL_TRC_CF(data, cf, "[%" PRId64 "] error after response headers, "
@@ -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,698 +821,229 @@ 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);
goto out;
- }
- }
-
- if(*pnread) {
- Curl_multi_mark_dirty(data);
- }
- else {
- if(stream->xfer_result) {
- CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id);
- cf_ngtcp2_proxy_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);
- if(nread > 0)
- *pnread = (size_t)nread;
- goto out;
- }
- result = CURLE_AGAIN;
- }
-
-out:
- result = Curl_1st_fatal(result,
- proxy_h3_progress_egress_ngtcp2(cf, data, &pktx));
- result = Curl_1st_fatal(result,
- check_and_set_expiry_ngtcp2(cf, data, &pktx));
-denied:
- CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(len=%zu) -> %d, %zu",
- stream ? stream->id : -1, len, result, *pnread);
- CF_DATA_RESTORE(cf, save);
- return result;
-}
-
-static void proxy_h3_submit(int64_t *pstream_id,
- struct Curl_cfilter *cf,
- struct Curl_easy *data,
- struct httpreq *req,
- CURLcode *err)
-{
- 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 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;
-
- Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
- *err = Curl_http_req_to_h2(&h2_headers, req, data);
- if(*err)
- goto out;
-
- *err = h3_proxy_data_setup(cf, data);
- if(*err)
- goto out;
-
- if(!ctx) {
- *err = CURLE_FAILED_INIT;
- goto out;
- }
-
- stream = H3_PROXY_STREAM_CTX(ctx, data);
-
- DEBUGASSERT(stream);
- if(!stream) {
- *err = 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;
- goto out;
- }
-
- for(i = 0; i < nheader; ++i) {
- struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
- nva[i].name = (unsigned char *)e->name;
- nva[i].namelen = e->namelen;
- nva[i].value = (unsigned char *)e->value;
- nva[i].valuelen = e->valuelen;
- nva[i].flags = NGHTTP3_NV_FLAG_NONE;
- }
-
- /* Open a bidirectional stream */
- {
- int64_t sid;
- int rv;
-
- DEBUGASSERT(stream->id == -1);
- 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;
- 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;
- 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;
- preader = &reader;
-
- rc = nghttp3_conn_submit_request(ctx->h3conn, H3_STREAM_ID(stream),
- 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));
- break;
- default:
- CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send -> %d (%s)",
- H3_STREAM_ID(stream), rc, nghttp3_strerror(rc));
- break;
- }
- *err = 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),
- 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;
-
- 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);
-}
-
-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))
- 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)
-{
- 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");
+
+ if(*pnread) {
+ Curl_multi_mark_dirty(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;
- }
+ if(stream->xfer_result) {
+ CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id);
+ Curl_cf_ngtcp2_h3_stream_close(cf, data, stream);
+ result = stream->xfer_result;
+ goto out;
+ }
+ else if(stream->closed) {
+ ssize_t nread =
+ cf_h3_proxy_recv_closed_stream(cf, data, stream, &result);
+ if(nread > 0)
+ *pnread = (size_t)nread;
+ goto out;
}
+ result = CURLE_AGAIN;
}
-#else /* not supported in the TLS backend */
- (void)data;
- (void)ctx;
- (void)scs;
- (void)alpns;
-#endif
+
+out:
+ 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));
+denied:
+ CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(len=%zu) -> %d, %zu",
+ stream ? stream->id : -1, len, result, *pnread);
+ CF_DATA_RESTORE(cf, save);
return result;
}
-static CURLcode cf_h3_proxy_ctx_init(struct Curl_cfilter *cf,
- struct Curl_easy *data)
+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 = 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);
+ 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;
- /* Initialize connection IDs BEFORE creating the connection */
- ctx->dcid.datalen = NGTCP2_MAX_CIDLEN;
- result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN);
+ Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
+ result = Curl_http_req_to_h2(&h2_headers, req, data);
if(result)
goto out;
- ctx->scid.datalen = NGTCP2_MAX_CIDLEN;
- result = Curl_rand(data, ctx->scid.data, NGTCP2_MAX_CIDLEN);
+ result = Curl_cf_ngtcp2_h3_stream_setup(cf, data);
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");
+ stream = H3_STREAM_CTX(ctx, data);
+ DEBUGASSERT(stream);
+ if(!stream) {
+ result = CURLE_FAILED_INIT;
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)
+ nheader = Curl_dynhds_count(&h2_headers);
+ nva = curlx_malloc(sizeof(nghttp3_nv) * nheader);
+ if(!nva) {
+ result = CURLE_OUT_OF_MEMORY;
goto out;
+ }
- /* Set ngtcp2_ctx in proxy_ctx BEFORE proxy_pktx_init which accesses it */
- proxy_ctx->ngtcp2_ctx = ctx;
+ for(i = 0; i < nheader; ++i) {
+ struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
+ nva[i].name = (unsigned char *)e->name;
+ nva[i].namelen = e->namelen;
+ nva[i].value = (unsigned char *)e->value;
+ nva[i].valuelen = e->valuelen;
+ nva[i].flags = NGHTTP3_NV_FLAG_NONE;
+ }
- /* Now we can safely initialize pktx and settings */
- proxy_pktx_init(&pktx, cf, data);
- quic_settings_proxy(ctx, data, &pktx);
+ /* Open a bidirectional stream */
+ {
+ int64_t sid;
+ int rv;
- 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);
+ DEBUGASSERT(stream->id == -1);
+ rv = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, data);
+ if(rv) {
+ failf(data, "cannot get bidi streams: %s", ngtcp2_strerror(rv));
+ result = CURLE_SEND_ERROR;
+ goto out;
+ }
+ stream->id = sid;
+ ++ctx->used_bidi_streams;
- 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;
+ /* 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);
}
- ctx->conn_ref.get_conn = proxy_get_conn;
- ctx->conn_ref.user_data = cf;
+ /* CONNECT-UDP request stream remains open for capsules, no fixed EOF. */
+ stream->send_closed = 0;
+ reader.read_data = cb_h3_tunnel_read_data;
+ preader = &reader;
- 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;
+ rc = nghttp3_conn_submit_request(ctx->h3conn, stream->id,
+ nva, nheader, preader, data);
-#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;
+ if(rc) {
+ switch(rc) {
+ case NGHTTP3_ERR_CONN_CLOSING:
+ CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send, "
+ "connection is closing", stream->id);
+ break;
+ default:
+ CURL_TRC_CF(data, cf, "h3sid[%" PRId64 "] failed to send -> %d (%s)",
+ stream->id, rc, nghttp3_strerror(rc));
+ break;
+ }
+ result = CURLE_SEND_ERROR;
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;
+
+ if(Curl_trc_is_verbose(data)) {
+ CURL_TRC_CF(data, cf, "[H3-PROXY] [%" PRId64 "] OPENED stream "
+ "for %s", stream->id,
+ Curl_bufref_ptr(&data->state.url));
}
-#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);
+out:
+ curlx_free(nva);
+ Curl_dynhds_free(&h2_headers);
+ return result;
+}
+
+static CURLcode cf_h3_proxy_adjust_pollset(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct easy_pollset *ps)
+{
+ 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);
- proxy_ctx->connected = FALSE;
+ if(!ctx->qconn || !pctx->tunnel.stream || (sock == CURL_SOCKET_BAD))
+ return CURLE_OK;
-out:
- if(result) {
- if(ctx) {
- proxy_ctx->ngtcp2_ctx = NULL; /* Clear before freeing on error */
- cf_ngtcp2_proxy_ctx_free(ctx);
- }
+ 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);
}
- 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)
+static bool cf_h3_proxy_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ 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;
+}
+
+static CURLcode cf_h3_proxy_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)
lib/vquic/cf-ngtcp2-proxy.h+12 -7
@@ -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 */
lib/vquic/cf-ngtcp2.c+91 -2085
@@ -25,28 +25,6 @@
#if !defined(CURL_DISABLE_HTTP) && defined(USE_NGTCP2) && defined(USE_NGHTTP3)
-#include <ngtcp2/ngtcp2.h>
-
-#ifdef USE_OPENSSL
-#include <openssl/err.h>
-#if defined(OPENSSL_IS_AWSLC) || defined(OPENSSL_IS_BORINGSSL)
-#include <ngtcp2/ngtcp2_crypto_boringssl.h>
-#elif defined(OPENSSL_QUIC_API2)
-#include <ngtcp2/ngtcp2_crypto_ossl.h>
-#else
-#include <ngtcp2/ngtcp2_crypto_quictls.h>
-#endif
-#include "vtls/openssl.h"
-#elif defined(USE_GNUTLS)
-#include <ngtcp2/ngtcp2_crypto_gnutls.h>
-#include "vtls/gtls.h"
-#elif defined(USE_WOLFSSL)
-#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
-#include "vtls/wolfssl.h"
-#endif
-
-#include <nghttp3/nghttp3.h>
-
#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 <v1.22.0 */
-static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid,
- uint8_t *token, size_t cidlen,
- void *user_data)
-{
- CURLcode result;
- (void)tconn;
- (void)user_data;
-
- result = Curl_rand(NULL, cid->data, cidlen);
- if(result)
- return NGTCP2_ERR_CALLBACK_FAILURE;
- cid->datalen = cidlen;
-
- result = Curl_rand(NULL, token, NGTCP2_STATELESS_RESET_TOKENLEN);
- if(result)
- return NGTCP2_ERR_CALLBACK_FAILURE;
-
- return 0;
-}
-
-#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 <v1.22.0 */
- NULL, /* remove_connection_id */
- ngtcp2_crypto_update_key_cb, /* update_key */
- NULL, /* path_validation */
- NULL, /* select_preferred_addr */
- cb_stream_reset,
- NULL, /* extend_max_remote_streams_bidi */
- NULL, /* extend_max_remote_streams_uni */
- cb_extend_max_stream_data,
- NULL, /* dcid_status */
- NULL, /* handshake_confirmed */
- NULL, /* recv_new_token */
- ngtcp2_crypto_delete_crypto_aead_ctx_cb,
- ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
- NULL, /* recv_datagram */
- NULL, /* ack_datagram */
- NULL, /* lost_datagram */
- ngtcp2_crypto_get_path_challenge_data_cb,
- cb_stream_stop_sending,
- NULL, /* version_negotiation */
- cb_recv_rx_key,
- NULL, /* recv_tx_key */
- NULL, /* early_data_rejected */
-#ifdef NGTCP2_CALLBACKS_V2 /* ngtcp2 v1.14.0+ */
- NULL, /* begin_path_validation */
-#endif
-#ifdef NGTCP2_CALLBACKS_V3 /* ngtcp2 v1.22.0+ */
- NULL, /* recv_stateless_reset2 */
- cb_get_new_connection_id2, /* get_new_connection_id2 */
- NULL, /* dcid_status2 */
- ngtcp2_crypto_get_path_challenge_data2_cb, /* get_path_challenge_data2 */
-#endif
-};
-
-#if defined(_MSC_VER) && defined(_DLL)
-#pragma warning(pop)
-#endif
-
-/**
- * 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(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;
- 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;
}
@@ -1795,405 +832,51 @@ static CURLcode cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
if(stream->resp_hds_complete) {
/* Server decided to close the stream after having sent us a final
* response. This is valid if it is not interested in the request
- * body. This happens on 30x or 40x responses.
- * We silently discard the data sent, since this is not a transport
- * error situation. */
- CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data"
- "on closed stream with response", stream->id);
- result = CURLE_OK;
- *pnwritten = len;
- goto out;
- }
- CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) "
- "-> stream closed", stream->id, len);
- result = CURLE_HTTP3;
- goto out;
- }
- else if(ctx->shutdown_started) {
- CURL_TRC_CF(data, cf, "cannot send on closed connection");
- result = CURLE_SEND_ERROR;
- goto out;
- }
- else {
- result = Curl_bufq_write(&stream->sendbuf, buf, len, pnwritten);
- CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to "
- "sendbuf(len=%zu) -> %d, %zu",
- stream->id, len, result, *pnwritten);
- if(result)
- goto out;
- (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id);
- }
-
- if(*pnwritten > 0 && !ctx->tls_handshake_complete && ctx->use_earlydata)
- ctx->earlydata_skip += *pnwritten;
-
- DEBUGASSERT(!result);
- result = cf_progress_egress(cf, data, &pktx);
-
-out:
- result = Curl_1st_fatal(result, check_and_set_expiry(cf, data, &pktx));
- if(ctx->tls_vrfy_result)
- result = ctx->tls_vrfy_result;
-denied:
- CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %d, %zu",
- stream ? stream->id : -1, len, result, *pnwritten);
- CF_DATA_RESTORE(cf, save);
- 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;
+ * body. This happens on 30x or 40x responses.
+ * We silently discard the data sent, since this is not a transport
+ * error situation. */
+ CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data"
+ "on closed stream with response", stream->id);
+ result = CURLE_OK;
+ *pnwritten = len;
+ goto out;
}
+ CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) "
+ "-> stream closed", stream->id, len);
+ result = CURLE_HTTP3;
+ goto out;
}
-}
-
-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 if(ctx->shutdown_started) {
+ CURL_TRC_CF(data, cf, "cannot send on closed connection");
+ result = CURLE_SEND_ERROR;
+ goto out;
}
else {
- pktx_update_time(data, pktx, cf);
- ngtcp2_path_storage_zero(&pktx->ps);
+ result = Curl_bufq_write(&stream->sendbuf, buf, len, pnwritten);
+ CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to "
+ "sendbuf(len=%zu) -> %d, %zu",
+ stream->id, len, result, *pnwritten);
+ if(result)
+ goto out;
+ (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id);
}
- result = vquic_flush(cf, data, &ctx->q);
- if(result) {
- if(result == CURLE_AGAIN) {
- Curl_expire(data, 1, EXPIRE_QUIC);
- return CURLE_OK;
- }
- return result;
- }
+ if(*pnwritten > 0 && !ctx->tls_handshake_complete && ctx->use_earlydata)
+ ctx->earlydata_skip += *pnwritten;
- /* 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;
- }
- }
- }
+ DEBUGASSERT(!result);
+ result = Curl_cf_ngtcp2_progress_egress(cf, data, &pktx);
- 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;
+out:
+ 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:
+ CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %d, %zu",
+ stream ? stream->id : -1, len, result, *pnwritten);
+ CF_DATA_RESTORE(cf, save);
+ return result;
}
static CURLcode h3_data_pause(struct Curl_cfilter *cf,
@@ -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;
}
lib/vquic/cf-ngtcp2.h+8 -6
@@ -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 */
lib/vquic/cf-quiche.c+47 -11
@@ -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
lib/vquic/cf-quiche.h+8 -3
@@ -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 */
lib/vquic/vquic-tls.c+22 -20
@@ -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);
lib/vquic/vquic-tls.h+7 -2
@@ -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,
lib/vquic/vquic.c+48 -19
@@ -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
}
lib/vquic/vquic.h+17 -7
@@ -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;
lib/vtls/apple.c+1 -1
@@ -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;
lib/vtls/gtls.c+3 -3
@@ -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);
lib/vtls/mbedtls.c+2 -2
@@ -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. */
lib/vtls/openssl.c+15 -22
@@ -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;
}
lib/vtls/rustls.c+1 -1
@@ -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");
lib/vtls/schannel.c+5 -5
@@ -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) {
lib/vtls/schannel_verify.c+1 -1
@@ -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;
lib/vtls/vtls.c+60 -50
@@ -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;
- }
-
- /* hostname MUST exist and not be empty */
- if(!dest) {
- result = CURLE_FAILED_INIT;
- goto out;
+ if(!ssl_peer || !origin) {
+ DEBUGASSERT(0);
+ return CURLE_FAILED_INIT;
}
-
- Curl_peer_link(&peer->dest, dest);
- peer->type = get_peer_type(dest->hostname);
- if(peer->type == CURL_SSL_PEER_DNS) {
+ DEBUGASSERT(!ssl_peer->origin);
+ DEBUGASSERT(!ssl_peer->peer);
+ DEBUGASSERT(!ssl_peer->sni);
+ ssl_peer->transport = transport;
+
+ 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;
}
lib/vtls/vtls.h+13 -6
@@ -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
lib/vtls/vtls_scache.c+56 -49
@@ -177,69 +177,78 @@ 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)
{
- struct dynbuf buf;
- size_t key_len;
- bool is_local = FALSE;
- CURLcode result;
-
- *ppeer_key = NULL;
- curlx_dyn_init(&buf, 10 * 1024);
-
- result = curlx_dyn_addf(&buf, "%s:%d",
- peer->dest->hostname, peer->dest->port);
- if(result)
- goto out;
-
- switch(peer->transport) {
+ switch(transport) {
case TRNSPRT_TCP:
- break;
+ return CURLE_OK;
case TRNSPRT_UDP:
- result = curlx_dyn_add(&buf, ":UDP");
- break;
+ return curlx_dyn_add(buf, ":UDP");
case TRNSPRT_QUIC:
- result = curlx_dyn_add(&buf, ":QUIC");
- break;
+ return curlx_dyn_add(buf, ":QUIC");
case TRNSPRT_UNIX:
- result = curlx_dyn_add(&buf, ":UNIX");
- break;
+ return curlx_dyn_add(buf, ":UNIX");
default:
- result = curlx_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport);
- break;
+ return curlx_dyn_addf(buf, ":TRNSPRT-%d", transport);
}
- if(result)
- goto out;
+}
+
+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");
+ result = curlx_dyn_add(buf, ":NO-VRFY-PEER");
if(result)
- goto out;
+ return result;
}
if(!ssl->verifyhost) {
- result = curlx_dyn_add(&buf, ":NO-VRFY-HOST");
+ result = curlx_dyn_add(buf, ":NO-VRFY-HOST");
if(result)
- goto out;
+ return result;
}
if(ssl->verifystatus) {
- result = curlx_dyn_add(&buf, ":VRFY-STATUS");
+ result = curlx_dyn_add(buf, ":VRFY-STATUS");
if(result)
- goto out;
+ return result;
}
- 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->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;
+ bool is_local = FALSE;
+ CURLcode result;
+
+ *ppeer_key = NULL;
+ curlx_dyn_init(&buf, 10 * 1024);
+ result = curlx_dyn_addf(&buf, "%s:%d",
+ peer->origin->hostname, peer->origin->port);
+ if(result)
+ goto out;
+ 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->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 {
lib/vtls/vtls_scache.h+3 -19
@@ -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.
lib/vtls/wolfssl.c+3 -3
@@ -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) {
tests/http/test_06_eyeballs.py+13 -0
@@ -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'
tests/unit/unit2600.c+11 -6
@@ -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)
tests/unit/unit3304.c+19 -19
@@ -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");