ngtcp2: share common functionality
No binary (build failed)
—
(log yok)
| @@ -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 \ | |
| @@ -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; | |
| @@ -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; | |
| @@ -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; | |
| @@ -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. | |
| @@ -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) | |
| { | |
| @@ -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, | |
| @@ -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; | |
| @@ -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) | |
| @@ -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; | |
| @@ -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 */ | |
| @@ -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) | |
| @@ -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(); | |
| @@ -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 */ | |
| @@ -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 */ | |
| @@ -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]; | |
| @@ -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 */ |
| @@ -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 */ |
| @@ -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) | |
| @@ -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 */ |
| @@ -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; | |
| } | |
| @@ -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 */ |
| @@ -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 | |
| @@ -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 */ |
| @@ -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); | |
| @@ -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, | |
| @@ -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 | |
| } | |
| @@ -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; | |
| @@ -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; | |
| @@ -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); | |
| @@ -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. */ | |
| @@ -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; | |
| } | |
| @@ -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"); | |
| @@ -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) { | |
| @@ -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; | |
| @@ -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; | |
| } | |
| @@ -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 | |
| @@ -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 { | |
| @@ -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. | |
| @@ -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) { | |
| @@ -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' |
| @@ -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) | |
| @@ -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"); | |