h3-proxy: fixes around H3 proxy
No binary (build failed)
—
(log yok)
| @@ -224,29 +224,47 @@ struct Curl_cftype Curl_cft_capsule = { | |
| Curl_cf_def_query, | |
| }; | |
| - | CURLcode Curl_cf_capsule_insert_after(struct Curl_cfilter *cf_at, |
| - | struct Curl_easy *data) |
| + | CURLcode Curl_cf_capsule_create(struct Curl_cfilter **pcf, |
| + | struct Curl_easy *data, |
| + | struct connectdata *conn) |
| { | |
| - | struct Curl_cfilter *cf; |
| + | struct Curl_cfilter *cf = NULL; |
| struct cf_capsule_ctx *ctx; | |
| CURLcode result; | |
| (void)data; | |
| + | (void)conn; |
| + | *pcf = NULL; |
| ctx = curlx_calloc(1, sizeof(*ctx)); | |
| - | if(!ctx) |
| - | return CURLE_OUT_OF_MEMORY; |
| + | if(!ctx) { |
| + | result = CURLE_OUT_OF_MEMORY; |
| + | goto out; |
| + | } |
| Curl_bufq_init2(&ctx->recvbuf, CAPSULE_CHUNK_SIZE, CAPSULE_RECV_CHUNKS, | |
| BUFQ_OPT_SOFT_LIMIT); | |
| result = Curl_cf_create(&cf, &Curl_cft_capsule, ctx); | |
| - | if(result) { |
| + | |
| + | out: |
| + | *pcf = (!result) ? cf : NULL; |
| + | if(result && ctx) { |
| Curl_bufq_free(&ctx->recvbuf); | |
| curlx_free(ctx); | |
| - | return result; |
| } | |
| - | Curl_conn_cf_insert_after(cf_at, cf); |
| - | return CURLE_OK; |
| + | return result; |
| + | } |
| + | |
| + | CURLcode Curl_cf_capsule_insert_after(struct Curl_cfilter *cf_at, |
| + | struct Curl_easy *data) |
| + | { |
| + | struct Curl_cfilter *cf; |
| + | CURLcode result; |
| + | |
| + | result = Curl_cf_capsule_create(&cf, data, cf_at->conn); |
| + | if(!result) |
| + | Curl_conn_cf_insert_after(cf_at, cf); |
| + | return result; |
| } | |
| #endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ | |
| @@ -27,6 +27,10 @@ | |
| #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) | |
| + | CURLcode Curl_cf_capsule_create(struct Curl_cfilter **pcf, |
| + | struct Curl_easy *data, |
| + | struct connectdata *conn); |
| + | |
| /* Insert a capsule protocol filter after `cf_at` in the filter chain. | |
| * The capsule filter encapsulates/decapsulates UDP datagrams using | |
| * the HTTP Datagram capsule format (RFC 9297). */ | |
| @@ -381,6 +381,7 @@ static CURLcode proxy_h2_progress_ingress(struct Curl_cfilter *cf, | |
| break; | |
| } | |
| else if(nread == 0) { | |
| + | CURL_TRC_CF(data, cf, "server closed connection"); |
| ctx->conn_closed = TRUE; | |
| break; | |
| } | |
| @@ -832,6 +833,11 @@ static CURLcode H2_CONNECT(struct Curl_cfilter *cf, | |
| DEBUGASSERT(ts); | |
| DEBUGASSERT(ts->authority); | |
| + | if(ctx->conn_closed) { |
| + | failf(data, "proxy closed connection"); |
| + | return CURLE_COULDNT_CONNECT; |
| + | } |
| + | |
| do { | |
| switch(ts->state) { | |
| case H2_TUNNEL_INIT: | |
| @@ -53,6 +53,7 @@ | |
| #include "sendf.h" | |
| #include "multiif.h" | |
| #include "cfilters.h" | |
| + | #include "cf-capsule.h" |
| #include "cf-socket.h" | |
| #include "connect.h" | |
| #include "progress.h" | |
| @@ -3057,6 +3058,10 @@ static CURLcode cf_h3_proxy_quic_connect(struct Curl_cfilter *cf, | |
| } | |
| *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); | |
| @@ -3414,6 +3419,63 @@ struct Curl_cftype Curl_cft_h3_proxy = { | |
| cf_h3_proxy_query, | |
| }; | |
| + | CURLcode Curl_cf_h3_proxy_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_cfilter *cf = NULL; |
| + | struct cf_h3_proxy_ctx *ctx; |
| + | CURLcode result = CURLE_OUT_OF_MEMORY; |
| + | |
| + | if((transport_out != TRNSPRT_QUIC) || (!conn->http_proxy.peer)) |
| + | return CURLE_FAILED_INIT; |
| + | |
| + | ctx = curlx_calloc(1, sizeof(*ctx)); |
| + | if(!ctx) { |
| + | result = CURLE_OUT_OF_MEMORY; |
| + | goto out; |
| + | } |
| + | ctx->udp_tunnel = (transport_in == TRNSPRT_QUIC); |
| + | |
| + | 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); |
| + | if(result) |
| + | goto out; |
| + | cf->next->conn = cf->conn; |
| + | cf->next->sockindex = cf->sockindex; |
| + | |
| + | if(ctx->udp_tunnel) { |
| + | struct Curl_cfilter *cf_caps = NULL; |
| + | result = Curl_cf_capsule_create(&cf_caps, data, conn); |
| + | if(result) |
| + | goto out; |
| + | cf_caps->conn = conn; |
| + | cf_caps->sockindex = cf->sockindex; |
| + | cf_caps->next = cf; |
| + | cf = cf_caps; |
| + | } |
| + | |
| + | out: |
| + | *pcf = (!result) ? cf : NULL; |
| + | if(result) { |
| + | if(cf) |
| + | Curl_conn_cf_discard_chain(&cf, data); |
| + | else if(ctx) |
| + | cf_h3_proxy_ctx_free(ctx); |
| + | } |
| + | else |
| + | CURL_TRC_CF(data, cf, "created, udp_tunnel=%d", ctx->udp_tunnel); |
| + | return result; |
| + | } |
| + | |
| CURLcode Curl_cf_h3_proxy_insert_after(struct Curl_cfilter *cf_at, | |
| struct Curl_easy *data, | |
| struct Curl_peer *dest, | |
| @@ -35,6 +35,13 @@ CURLcode Curl_cf_h3_proxy_insert_after(struct Curl_cfilter *cf_at, | |
| struct Curl_peer *dest, | |
| bool udp_tunnel); | |
| + | CURLcode Curl_cf_h3_proxy_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); |
| + | |
| extern struct Curl_cftype Curl_cft_h3_proxy; | |
| #endif | |
| @@ -51,6 +51,7 @@ | |
| #include "cfilters.h" | |
| #include "cf-dns.h" | |
| #include "cf-ip-happy.h" | |
| + | #include "cf-h3-proxy.h" |
| #include "curl_addrinfo.h" | |
| #include "curl_trc.h" | |
| #include "multiif.h" | |
| @@ -60,8 +61,9 @@ | |
| struct transport_provider { | |
| - | uint8_t transport; |
| cf_ip_connect_create *cf_create; | |
| + | uint8_t transport; |
| + | bool tunnel_proxy; |
| }; | |
| static | |
| @@ -69,23 +71,30 @@ static | |
| const | |
| #endif | |
| struct transport_provider transport_providers[] = { | |
| - | { TRNSPRT_TCP, Curl_cf_tcp_create }, |
| + | { Curl_cf_tcp_create, TRNSPRT_TCP, FALSE }, |
| + | { Curl_cf_tcp_create, TRNSPRT_TCP, TRUE }, |
| #if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3) | |
| - | { TRNSPRT_QUIC, Curl_cf_quic_create }, |
| + | { Curl_cf_quic_create, TRNSPRT_QUIC, FALSE }, |
| + | #endif |
| + | #if !defined(CURL_DISABLE_HTTP) && defined(USE_PROXY_HTTP3) |
| + | { Curl_cf_h3_proxy_create, TRNSPRT_QUIC, TRUE }, |
| #endif | |
| #ifndef CURL_DISABLE_TFTP | |
| - | { TRNSPRT_UDP, Curl_cf_udp_create }, |
| + | { Curl_cf_udp_create, TRNSPRT_UDP, FALSE }, |
| #endif | |
| #ifdef USE_UNIX_SOCKETS | |
| - | { TRNSPRT_UNIX, Curl_cf_unix_create }, |
| + | { Curl_cf_unix_create, TRNSPRT_UNIX, FALSE }, |
| + | { Curl_cf_unix_create, TRNSPRT_UNIX, TRUE }, |
| #endif | |
| }; | |
| - | static cf_ip_connect_create *get_cf_create(uint8_t transport) |
| + | static cf_ip_connect_create *get_cf_create(uint8_t transport, |
| + | bool tunnel_proxy) |
| { | |
| size_t i; | |
| for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) { | |
| - | if(transport == transport_providers[i].transport) |
| + | if((transport == transport_providers[i].transport) && |
| + | (tunnel_proxy == transport_providers[i].tunnel_proxy)) |
| return transport_providers[i].cf_create; | |
| } | |
| return NULL; | |
| @@ -102,7 +111,6 @@ UNITTEST void debug_set_transport_provider( | |
| for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) { | |
| if(transport == transport_providers[i].transport) { | |
| transport_providers[i].cf_create = cf_create; | |
| - | return; |
| } | |
| } | |
| } | |
| @@ -154,7 +162,8 @@ struct cf_ip_attempt { | |
| struct curltime started; /* start of current attempt */ | |
| CURLcode result; | |
| int ai_family; | |
| - | uint8_t transport; |
| + | uint8_t transport_in; |
| + | uint8_t transport_out; |
| int error; | |
| BIT(connected); /* cf has connected */ | |
| BIT(shutdown); /* cf has shutdown */ | |
| @@ -177,7 +186,8 @@ static CURLcode cf_ip_attempt_new(struct cf_ip_attempt **pa, | |
| struct Curl_easy *data, | |
| struct Curl_sockaddr_ex *addr, | |
| int ai_family, | |
| - | uint8_t transport, |
| + | uint8_t transport_in, |
| + | uint8_t transport_out, |
| cf_ip_connect_create *cf_create) | |
| { | |
| struct Curl_cfilter *wcf; | |
| @@ -191,12 +201,14 @@ static CURLcode cf_ip_attempt_new(struct cf_ip_attempt **pa, | |
| a->addr = *addr; | |
| a->ai_family = ai_family; | |
| - | a->transport = transport; |
| + | 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); |
| + | result = a->cf_create(&a->cf, data, cf->conn, &a->addr, |
| + | a->transport_in, a->transport_out); |
| if(result) | |
| goto out; | |
| @@ -251,7 +263,8 @@ struct cf_ip_ballers { | |
| timediff_t attempt_delay_ms; | |
| int last_attempt_ai_family; | |
| uint32_t max_concurrent; | |
| - | uint8_t transport; |
| + | uint8_t transport_in; |
| + | uint8_t transport_out; |
| }; | |
| static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a, | |
| @@ -269,7 +282,8 @@ 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); |
| + | result = a->cf_create(&a->cf, data, cf->conn, &a->addr, a->transport_in, |
| + | a->transport_out); |
| if(!result) { | |
| bool dummy; | |
| /* the new filter might have sub-filters */ | |
| @@ -299,18 +313,20 @@ static void cf_ip_ballers_clear(struct Curl_cfilter *cf, | |
| static CURLcode cf_ip_ballers_init(struct cf_ip_ballers *bs, | |
| struct Curl_cfilter *cf, | |
| cf_ip_connect_create *cf_create, | |
| - | uint8_t transport, |
| + | uint8_t transport_in, |
| + | uint8_t transport_out, |
| timediff_t attempt_delay_ms, | |
| uint32_t max_concurrent) | |
| { | |
| memset(bs, 0, sizeof(*bs)); | |
| bs->cf_create = cf_create; | |
| - | bs->transport = transport; |
| + | bs->transport_in = transport_in; |
| + | bs->transport_out = transport_out; |
| 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 == TRNSPRT_UNIX) { |
| + | if(transport_in == TRNSPRT_UNIX) { |
| #ifdef USE_UNIX_SOCKETS | |
| cf_ai_iter_init(&bs->addr_iter, cf, AF_UNIX); | |
| #else | |
| @@ -458,12 +474,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); |
| + | result = Curl_socket_addr_from_ai(&addr, ai, bs->transport_out); |
| if(result) | |
| goto out; | |
| result = cf_ip_attempt_new(&a, cf, data, &addr, ai_family, | |
| - | bs->transport, bs->cf_create); |
| + | bs->transport_in, bs->transport_out, |
| + | bs->cf_create); |
| CURL_TRC_CF(data, cf, "starting %s attempt for ipv%s -> %d", | |
| bs->running ? "next" : "first", | |
| (ai_family == AF_INET) ? "4" : "6", result); | |
| @@ -652,11 +669,13 @@ typedef enum { | |
| } cf_connect_state; | |
| struct cf_ip_happy_ctx { | |
| - | uint8_t transport; |
| + | 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); | |
| }; | |
| @@ -732,10 +751,11 @@ static CURLcode cf_ip_happy_init(struct Curl_cfilter *cf, | |
| return CURLE_OPERATION_TIMEDOUT; | |
| } | |
| - | CURL_TRC_CF(data, cf, "init ip ballers for transport %u", ctx->transport); |
| + | CURL_TRC_CF(data, cf, "init ip ballers for transport %u", |
| + | ctx->transport_out); |
| ctx->started = *Curl_pgrs_now(data); | |
| - | return cf_ip_ballers_init(&ctx->ballers, cf, |
| - | ctx->cf_create, ctx->transport, |
| + | 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); | |
| } | |
| @@ -752,8 +772,10 @@ static void cf_ip_happy_ctx_clear(struct Curl_cfilter *cf, | |
| static void cf_ip_happy_ctx_destroy(struct cf_ip_happy_ctx *ctx) | |
| { | |
| - | if(ctx) |
| + | if(ctx) { |
| + | Curl_peer_unlink(&ctx->peer); |
| curlx_free(ctx); | |
| + | } |
| } | |
| static CURLcode cf_ip_happy_shutdown(struct Curl_cfilter *cf, | |
| @@ -973,9 +995,11 @@ struct Curl_cftype Curl_cft_ip_happy = { | |
| */ | |
| static CURLcode cf_ip_happy_create(struct Curl_cfilter **pcf, | |
| struct Curl_easy *data, | |
| + | struct Curl_peer *peer, |
| struct connectdata *conn, | |
| cf_ip_connect_create *cf_create, | |
| - | uint8_t transport) |
| + | uint8_t transport_in, |
| + | uint8_t transport_out) |
| { | |
| struct cf_ip_happy_ctx *ctx = NULL; | |
| CURLcode result; | |
| @@ -988,8 +1012,10 @@ static CURLcode cf_ip_happy_create(struct Curl_cfilter **pcf, | |
| result = CURLE_OUT_OF_MEMORY; | |
| goto out; | |
| } | |
| - | ctx->transport = transport; |
| + | ctx->transport_in = transport_in; |
| + | ctx->transport_out = transport_out; |
| ctx->cf_create = cf_create; | |
| + | Curl_peer_link(&ctx->peer, peer); |
| result = Curl_cf_create(pcf, &Curl_cft_ip_happy, ctx); | |
| @@ -1003,7 +1029,10 @@ out: | |
| CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, | |
| struct Curl_easy *data, | |
| - | uint8_t transport) |
| + | struct Curl_peer *peer, |
| + | uint8_t transport_in, |
| + | uint8_t transport_out, |
| + | bool tunnel_proxy) |
| { | |
| cf_ip_connect_create *cf_create; | |
| struct Curl_cfilter *cf; | |
| @@ -1011,40 +1040,17 @@ CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, | |
| /* Need to be first */ | |
| DEBUGASSERT(cf_at); | |
| - | cf_create = get_cf_create(transport); |
| + | cf_create = get_cf_create(transport_out, tunnel_proxy); |
| if(!cf_create) { | |
| - | CURL_TRC_CF(data, cf_at, "unsupported transport type %u", transport); |
| + | 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, cf_at->conn, cf_create, transport); |
| - | if(result) |
| - | return result; |
| - | |
| - | Curl_conn_cf_insert_after(cf_at, cf); |
| - | return CURLE_OK; |
| - | } |
| - | |
| - | #if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3) && \ |
| - | defined(USE_PROXY_HTTP3) |
| - | CURLcode cf_ip_happy_quic_udp_insert_after(struct Curl_cfilter *cf_at, |
| - | struct Curl_easy *data) |
| - | { |
| - | /* For H3 proxy: create happy eyeballs that races IPv4/IPv6 using raw |
| - | UDP sockets with TRNSPRT_QUIC transport. Using TRNSPRT_QUIC causes |
| - | cf_udp_connect() to call cf_udp_setup_quic() which connects the |
| - | socket to the peer address, making send() work without an explicit |
| - | destination. We use Curl_cf_udp_create (not Curl_cf_quic_create) |
| - | because H3-PROXY manages its own ngtcp2 QUIC stack on top. */ |
| - | struct Curl_cfilter *cf; |
| - | CURLcode result; |
| - | |
| - | DEBUGASSERT(cf_at); |
| - | result = cf_ip_happy_create(&cf, data, cf_at->conn, |
| - | Curl_cf_udp_create, TRNSPRT_QUIC); |
| + | result = cf_ip_happy_create(&cf, data, peer, cf_at->conn, cf_create, |
| + | transport_in, transport_out); |
| if(result) | |
| return result; | |
| Curl_conn_cf_insert_after(cf_at, cf); | |
| return CURLE_OK; | |
| } | |
| - | #endif /* !CURL_DISABLE_HTTP && USE_HTTP3 && USE_PROXY_HTTP3 */ |
| @@ -29,6 +29,7 @@ struct connectdata; | |
| struct Curl_addrinfo; | |
| struct Curl_cfilter; | |
| struct Curl_easy; | |
| + | struct Curl_peer; |
| struct Curl_sockaddr_ex; | |
| /** | |
| @@ -46,11 +47,15 @@ typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf, | |
| struct Curl_easy *data, | |
| struct connectdata *conn, | |
| struct Curl_sockaddr_ex *addr, | |
| - | uint8_t transport); |
| + | uint8_t transport_in, |
| + | uint8_t transport_out); |
| CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at, | |
| struct Curl_easy *data, | |
| - | uint8_t transport); |
| + | 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) | |
| @@ -1768,7 +1768,8 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, | |
| struct Curl_easy *data, | |
| struct connectdata *conn, | |
| struct Curl_sockaddr_ex *addr, | |
| - | uint8_t transport) |
| + | uint8_t transport_in, |
| + | uint8_t transport_out) |
| { | |
| struct cf_socket_ctx *ctx = NULL; | |
| struct Curl_cfilter *cf = NULL; | |
| @@ -1776,7 +1777,8 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, | |
| (void)data; | |
| (void)conn; | |
| - | DEBUGASSERT(transport == TRNSPRT_TCP); |
| + | (void)transport_in; |
| + | DEBUGASSERT(transport_out == TRNSPRT_TCP); |
| if(!addr) { | |
| result = CURLE_BAD_FUNCTION_ARGUMENT; | |
| goto out; | |
| @@ -1788,7 +1790,7 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, | |
| goto out; | |
| } | |
| - | result = cf_socket_ctx_init(ctx, addr, transport); |
| + | result = cf_socket_ctx_init(ctx, addr, transport_out); |
| if(result) | |
| goto out; | |
| @@ -1934,7 +1936,8 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, | |
| struct Curl_easy *data, | |
| struct connectdata *conn, | |
| struct Curl_sockaddr_ex *addr, | |
| - | uint8_t transport) |
| + | uint8_t transport_in, |
| + | uint8_t transport_out) |
| { | |
| struct cf_socket_ctx *ctx = NULL; | |
| struct Curl_cfilter *cf = NULL; | |
| @@ -1942,14 +1945,15 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, | |
| (void)data; | |
| (void)conn; | |
| - | DEBUGASSERT(transport == TRNSPRT_UDP || transport == TRNSPRT_QUIC); |
| + | (void)transport_in; |
| + | DEBUGASSERT(transport_out == TRNSPRT_UDP || transport_out == 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); |
| + | result = cf_socket_ctx_init(ctx, addr, transport_out); |
| if(result) | |
| goto out; | |
| @@ -1988,7 +1992,8 @@ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, | |
| struct Curl_easy *data, | |
| struct connectdata *conn, | |
| struct Curl_sockaddr_ex *addr, | |
| - | uint8_t transport) |
| + | uint8_t transport_in, |
| + | uint8_t transport_out) |
| { | |
| struct cf_socket_ctx *ctx = NULL; | |
| struct Curl_cfilter *cf = NULL; | |
| @@ -1996,14 +2001,15 @@ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, | |
| (void)data; | |
| (void)conn; | |
| - | DEBUGASSERT(transport == TRNSPRT_UNIX); |
| + | (void)transport_in; |
| + | DEBUGASSERT(transport_out == 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); |
| + | result = cf_socket_ctx_init(ctx, addr, transport_out); |
| if(result) | |
| goto out; | |
| @@ -96,7 +96,8 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, | |
| struct Curl_easy *data, | |
| struct connectdata *conn, | |
| struct Curl_sockaddr_ex *addr, | |
| - | uint8_t transport); |
| + | uint8_t transport_in, |
| + | uint8_t transport_out); |
| /** | |
| * Creates a cfilter that opens a UDP socket to the given address | |
| @@ -109,7 +110,8 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, | |
| struct Curl_easy *data, | |
| struct connectdata *conn, | |
| struct Curl_sockaddr_ex *addr, | |
| - | uint8_t transport); |
| + | uint8_t transport_in, |
| + | uint8_t transport_out); |
| /** | |
| * Creates a cfilter that opens a UNIX socket to the given address | |
| @@ -122,7 +124,8 @@ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, | |
| struct Curl_easy *data, | |
| struct connectdata *conn, | |
| struct Curl_sockaddr_ex *addr, | |
| - | uint8_t transport); |
| + | uint8_t transport_in, |
| + | uint8_t transport_out); |
| /** | |
| * Creates a cfilter that keeps a listening socket. | |
| @@ -690,7 +690,9 @@ bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex) | |
| static bool cf_is_ssl(struct Curl_cfilter *cf) | |
| { | |
| for(; cf; cf = cf->next) { | |
| - | if(cf->cft->flags & CF_TYPE_SSL) |
| + | /* A tunneling proxy does not offer end2end encryption, even if |
| + | * it does SSL itself (e.g. QUIC H3 proxy) */ |
| + | if((cf->cft->flags & CF_TYPE_SSL) && !(cf->cft->flags & CF_TYPE_PROXY)) |
| return TRUE; | |
| if(cf->cft->flags & CF_TYPE_IP_CONNECT) | |
| return FALSE; | |
| @@ -342,72 +342,14 @@ struct cf_setup_ctx { | |
| uint8_t transport; | |
| }; | |
| - | #ifndef CURL_DISABLE_PROXY |
| - | static CURLcode cf_setup_add_http_proxy(struct Curl_cfilter *cf, |
| - | struct Curl_easy *data, |
| - | struct cf_setup_ctx *ctx) |
| - | { |
| - | CURLcode result = CURLE_OK; |
| - | #ifndef USE_SSL |
| - | (void)cf; |
| - | (void)data; |
| - | (void)ctx; |
| - | #else |
| - | /* Skipping the Curl_conn_is_ssl check because SSL is a part of QUIC |
| - | For CURLPROXY_HTTPS and CURLPROXY_HTTPS2: |
| - | Curl_cft_setup --> Curl_cft_ssl --> Curl_cft_http_proxy --> ... |
| - | For CURLPROXY_HTTPS3: |
| - | Curl_cft_setup --> Curl_cft_http3 --> Curl_cft_http_proxy --> ... */ |
| - | if(ctx->transport == TRNSPRT_QUIC && cf->conn->bits.httpproxy) { |
| - | if(!IS_QUIC_PROXY(cf->conn->http_proxy.proxytype)) { |
| - | result = Curl_cf_ssl_proxy_insert_after(cf, data); |
| - | if(result) |
| - | return result; |
| - | } |
| - | } |
| - | else { |
| - | if(IS_HTTPS_PROXY(cf->conn->http_proxy.proxytype) && |
| - | !Curl_conn_is_ssl(cf->conn, cf->sockindex) && |
| - | !IS_QUIC_PROXY(cf->conn->http_proxy.proxytype)) { |
| - | result = Curl_cf_ssl_proxy_insert_after(cf, data); |
| - | if(result) |
| - | return result; |
| - | } |
| - | } |
| - | #endif /* USE_SSL */ |
| - | |
| - | #ifndef CURL_DISABLE_HTTP |
| - | if(cf->conn->bits.tunnel_proxy) { |
| - | struct Curl_peer *dest; /* where HTTP should tunnel to */ |
| - | bool udp_tun = false; |
| - | dest = Curl_conn_get_destination(cf->conn, cf->sockindex); |
| - | /* Use CONNECT-UDP only for explicit HTTP/3-only target tunnels. |
| - | Do not derive this from proxy transport (for example HTTPS3 proxy). */ |
| - | if(data->state.http_neg.wanted == CURL_HTTP_V3x) { |
| - | #ifdef USE_PROXY_HTTP3 |
| - | udp_tun = TRUE; |
| - | #else |
| - | failf(data, "HTTP/3 proxy tunnel support not built-in"); |
| - | return CURLE_NOT_BUILT_IN; |
| - | #endif /* USE_PROXY_HTTP3 */ |
| - | } |
| - | result = Curl_cf_http_proxy_insert_after(cf, data, dest, |
| - | cf->conn->http_proxy.proxytype, |
| - | udp_tun); |
| - | if(result) |
| - | return result; |
| - | } |
| - | #endif /* !CURL_DISABLE_HTTP */ |
| - | return result; |
| - | } |
| - | #endif /* !CURL_DISABLE_PROXY */ |
| - | |
| static CURLcode cf_setup_connect(struct Curl_cfilter *cf, | |
| struct Curl_easy *data, | |
| bool *done) | |
| { | |
| struct cf_setup_ctx *ctx = cf->ctx; | |
| CURLcode result = CURLE_OK; | |
| + | struct Curl_peer *first_peer = |
| + | Curl_conn_get_first_peer(cf->conn, cf->sockindex); |
| if(cf->connected) { | |
| *done = TRUE; | |
| @@ -425,38 +367,38 @@ connect_sub_chain: | |
| } | |
| if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) { | |
| - | #ifndef CURL_DISABLE_PROXY |
| - | #if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3) && \ |
| - | defined(USE_PROXY_HTTP3) |
| - | if(IS_QUIC_PROXY(cf->conn->http_proxy.proxytype) && |
| - | cf->conn->bits.tunnel_proxy) { |
| - | /* For HTTPS3 proxy tunnels, H3-PROXY manages the QUIC connection |
| - | on top of the UDP socket. Let happy eyeballs race IPv4/IPv6 using |
| - | QUIC-transport UDP sockets so the socket is connected to the |
| - | proxy peer and H3-PROXY can send directly via send(). |
| - | Filter chains: |
| - | H1/H2 target (CONNECT over QUIC): |
| - | SETUP --> HTTP/1.1 or HTTP/2 --> SSL --> HTTP-PROXY --> |
| - | H3-PROXY --> HAPPY-EYEBALLS --> UDP |
| - | H3 target (MASQUE CONNECT-UDP over QUIC): |
| - | SETUP --> HTTP/3 --> CAPSULE --> HTTP-PROXY --> |
| - | H3-PROXY --> HAPPY-EYEBALLS --> UDP */ |
| - | result = cf_ip_happy_quic_udp_insert_after(cf, data); |
| + | /* What type of thing we do connect to first? |
| + | * - without a proxy, `ctx->transport` defines it |
| + | * - with non-tunneling proxy, `ctx->transport` also applies, but |
| + | * for QUIC we need the cf-h3-proxy, not the standard vquic one |
| + | * - with tunneling proxy, transport is defined by the proxytype |
| + | * chosen and `ctx->transport` is tunneled through it. |
| + | */ |
| + | uint8_t transport_out = ctx->transport; |
| + | bool tunnel_proxy = FALSE; |
| + | #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) |
| + | CURL_TRC_CF(data, cf, "happy eyeballing, httpproxy=%d, type=%d, " |
| + | "transport=%d", |
| + | cf->conn->bits.httpproxy, cf->conn->http_proxy.proxytype, |
| + | ctx->transport); |
| + | if(cf->conn->bits.httpproxy && cf->conn->bits.tunnel_proxy) { |
| + | transport_out = |
| + | Curl_http_proxy_transport(cf->conn->http_proxy.proxytype); |
| + | tunnel_proxy = TRUE; |
| + | if((transport_out == TRNSPRT_QUIC) && (cf->conn->bits.socksproxy)) { |
| + | failf(data, "HTTP/3 proxy not possible via SOCKS"); |
| + | return CURLE_UNSUPPORTED_PROTOCOL; |
| + | } |
| } | |
| - | /* When tunneling QUIC through an HTTP proxy (CONNECT-UDP), |
| - | the underlying conn to the proxy is TCP. */ |
| - | else |
| - | #endif /* !CURL_DISABLE_HTTP && USE_HTTP3 && USE_PROXY_HTTP3 */ |
| - | if(ctx->transport == TRNSPRT_QUIC && cf->conn->bits.httpproxy && |
| - | !IS_QUIC_PROXY(cf->conn->http_proxy.proxytype)) |
| - | result = cf_ip_happy_insert_after(cf, data, TRNSPRT_TCP); |
| - | else |
| - | #endif /* !CURL_DISABLE_PROXY */ |
| - | result = cf_ip_happy_insert_after(cf, data, ctx->transport); |
| + | #endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ |
| + | result = cf_ip_happy_insert_after(cf, data, first_peer, |
| + | ctx->transport, transport_out, |
| + | tunnel_proxy); |
| if(result) | |
| return result; | |
| - | ctx->state = CF_SETUP_CNNCT_EYEBALLS; |
| + | ctx->state = (tunnel_proxy && (transport_out == TRNSPRT_QUIC)) ? |
| + | CF_SETUP_CNNCT_HTTP_PROXY : CF_SETUP_CNNCT_EYEBALLS; |
| if(!cf->next || !cf->next->connected) | |
| goto connect_sub_chain; | |
| } | |
| @@ -491,9 +433,25 @@ connect_sub_chain: | |
| } | |
| if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) { | |
| - | result = cf_setup_add_http_proxy(cf, data, ctx); |
| - | if(result) |
| - | return result; |
| + | #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); |
| + | if(result) |
| + | return result; |
| + | } |
| + | #endif /* USE_SSL */ |
| + | |
| + | #ifndef CURL_DISABLE_HTTP |
| + | if(cf->conn->bits.tunnel_proxy) { |
| + | struct Curl_peer *dest; /* where HTTP should tunnel to */ |
| + | dest = 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); |
| + | if(result) |
| + | return result; |
| + | } |
| + | #endif /* !CURL_DISABLE_HTTP */ |
| ctx->state = CF_SETUP_CNNCT_HTTP_PROXY; | |
| if(!cf->next || !cf->next->connected) | |
| goto connect_sub_chain; | |
| @@ -503,9 +461,8 @@ connect_sub_chain: | |
| if(ctx->state < CF_SETUP_CNNCT_HAPROXY) { | |
| #ifndef CURL_DISABLE_PROXY | |
| if(data->set.haproxyprotocol) { | |
| - | if(Curl_conn_is_ssl(cf->conn, cf->sockindex)) { |
| - | failf(data, "haproxy protocol not supported with SSL " |
| - | "encryption in place (QUIC?)"); |
| + | if(ctx->transport == TRNSPRT_QUIC) { |
| + | failf(data, "haproxy protocol not support QUIC"); |
| return CURLE_UNSUPPORTED_PROTOCOL; | |
| } | |
| result = Curl_cf_haproxy_insert_after(cf, data); | |
| @@ -753,8 +753,8 @@ 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 proxytype, |
| - | bool udp_tunnel) |
| + | uint8_t transport, |
| + | uint8_t proxytype) |
| { | |
| struct Curl_cfilter *cf; | |
| struct cf_proxy_ctx *ctx = NULL; | |
| @@ -771,7 +771,7 @@ CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, | |
| } | |
| Curl_peer_link(&ctx->dest, dest); | |
| ctx->proxytype = proxytype; | |
| - | ctx->udp_tunnel = udp_tunnel; |
| + | ctx->udp_tunnel = (transport == TRNSPRT_QUIC); |
| result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx); | |
| if(result) | |
| @@ -784,4 +784,14 @@ out: | |
| return result; | |
| } | |
| + | uint8_t Curl_http_proxy_transport(uint8_t proxytype) |
| + | { |
| + | switch(proxytype) { |
| + | case CURLPROXY_HTTPS3: |
| + | return TRNSPRT_QUIC; |
| + | default: |
| + | return TRNSPRT_TCP; |
| + | } |
| + | } |
| + | |
| #endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */ | |
| @@ -69,8 +69,8 @@ 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 proxytype, |
| - | bool udp_tunnel); |
| + | uint8_t transport, |
| + | uint8_t proxytype); |
| extern struct Curl_cftype Curl_cft_http_proxy; | |
| @@ -83,4 +83,6 @@ extern struct Curl_cftype Curl_cft_http_proxy; | |
| #define IS_QUIC_PROXY(t) ((t) == CURLPROXY_HTTPS3) | |
| + | uint8_t Curl_http_proxy_transport(uint8_t proxytype); |
| + | |
| #endif /* HEADER_CURL_HTTP_PROXY_H */ | |
| @@ -1317,12 +1317,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) | |
| #endif | |
| conn->ip_version = data->set.ipver; | |
| conn->bits.connect_only = (bool)data->set.connect_only; | |
| - | #ifndef CURL_DISABLE_PROXY |
| - | if(conn->http_proxy.proxytype == CURLPROXY_HTTPS3) |
| - | conn->transport_wanted = TRNSPRT_QUIC; |
| - | else |
| - | #endif |
| - | conn->transport_wanted = TRNSPRT_TCP; /* most of them are TCP streams */ |
| + | conn->transport_wanted = TRNSPRT_TCP; /* most of them are TCP streams */ |
| /* Store the local bind parameters that will be used for this connection */ | |
| if(data->set.str[STRING_DEVICE]) { | |
| @@ -3120,7 +3120,8 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, | |
| goto out; | |
| cf->conn = conn; | |
| - | result = Curl_cf_udp_create(&cf->next, data, conn, addr, TRNSPRT_QUIC); |
| + | result = Curl_cf_udp_create(&cf->next, data, conn, addr, |
| + | TRNSPRT_QUIC, TRNSPRT_QUIC); |
| if(result) | |
| goto out; | |
| cf->next->conn = cf->conn; | |
| @@ -1659,7 +1659,8 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, | |
| goto out; | |
| cf->conn = conn; | |
| - | result = Curl_cf_udp_create(&cf->next, data, conn, addr, TRNSPRT_QUIC); |
| + | result = Curl_cf_udp_create(&cf->next, data, conn, addr, |
| + | TRNSPRT_QUIC, TRNSPRT_QUIC); |
| if(result) | |
| goto out; | |
| cf->next->conn = cf->conn; | |
| @@ -769,10 +769,12 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, | |
| struct Curl_easy *data, | |
| struct connectdata *conn, | |
| struct Curl_sockaddr_ex *addr, | |
| - | uint8_t transport) |
| + | uint8_t transport_in, |
| + | uint8_t transport_out) |
| { | |
| - | (void)transport; |
| - | DEBUGASSERT(transport == TRNSPRT_QUIC); |
| + | (void)transport_in; |
| + | (void)transport_out; |
| + | DEBUGASSERT(transport_out == TRNSPRT_QUIC); |
| #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) | |
| return Curl_cf_ngtcp2_create(pcf, data, conn, addr); | |
| #elif defined(USE_QUICHE) | |
| @@ -45,7 +45,8 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, | |
| struct Curl_easy *data, | |
| struct connectdata *conn, | |
| struct Curl_sockaddr_ex *addr, | |
| - | uint8_t transport); |
| + | uint8_t transport_in, |
| + | uint8_t transport_out); |
| extern struct Curl_cftype Curl_cft_http3; | |
| @@ -176,7 +176,7 @@ def h2o_server(env) -> Generator[Union[H2oServer, bool], None, None]: | |
| h2o_logs = "\n".join(h2o.dump_logs()) | |
| pytest.skip(f"h2o server failed to start\n{h2o_logs}") | |
| yield h2o | |
| - | h2o.stop() |
| + | h2o.kill() |
| else: | |
| yield False | |
| @@ -190,6 +190,6 @@ def h2o_proxy(env) -> Generator[Union[H2oProxy, bool], None, None]: | |
| h2o_logs = "\n".join(h2o.dump_logs()) | |
| pytest.skip(f"h2o proxy failed to start\n{h2o_logs}") | |
| yield h2o | |
| - | h2o.stop() |
| + | h2o.kill() |
| else: | |
| yield False | |
| @@ -95,29 +95,29 @@ def _check_download_size(curl: CurlClient, expected_size: int): | |
| def _nghttpx_proxy_args( | |
| env: Env, | |
| nghttpx, | |
| + | nghttpx_fwd, |
| proxy_proto: str, | |
| tunnel: bool, | |
| - | insecure: bool = False, |
| ): | |
| - | xargs = [ |
| - | "--proxy", |
| - | f"https://{env.proxy_domain}:{nghttpx._port}/", |
| - | "--resolve", |
| - | f"{env.proxy_domain}:{nghttpx._port}:127.0.0.1", |
| - | "--proxy-cacert", |
| - | env.ca.cert_file, |
| - | ] |
| + | port = env.pts_port(proxy_proto) |
| + | domain = env.proxy_domain |
| + | xxarg = None |
| if proxy_proto == "h3": | |
| - | xargs.append("--proxy-http3") |
| + | port = nghttpx.port |
| + | domain = env.domain1 |
| + | xxarg = "--proxy-http3" |
| elif proxy_proto == "h2": | |
| - | xargs.append("--proxy-http2") |
| + | xxarg = "--proxy-http2" |
| + | xargs = [ |
| + | "--proxy", f"https://{domain}:{port}/", |
| + | "--resolve", f"{domain}:{port}:127.0.0.1", |
| + | "--proxy-cacert", env.ca.cert_file |
| + | ] |
| + | if xxarg: |
| + | xargs.append(xxarg) |
| if tunnel: | |
| xargs.append("--proxytunnel") | |
| - | |
| - | xargs.extend(["--cacert", env.ca.cert_file, "--proxy-insecure"]) |
| - | if insecure: |
| - | xargs.append("--insecure") |
| return xargs | |
| @@ -126,22 +126,13 @@ def _h2o_proxy_args( | |
| h2o_proxy, | |
| proxy_proto: str, | |
| tunnel: bool, | |
| - | insecure: bool = False, |
| ): | |
| - | if proxy_proto == "h3": |
| - | pport = h2o_proxy.port |
| - | elif proxy_proto == "h2": |
| - | pport = h2o_proxy.h2_port |
| - | else: |
| - | pport = h2o_proxy.h1_port |
| - | |
| + | pport = env.pts_port(proxy_proto, use_h2o=True) |
| xargs = [ | |
| - | "--proxy", |
| - | f"https://{env.proxy_domain}:{pport}/", |
| - | "--resolve", |
| - | f"{env.proxy_domain}:{pport}:127.0.0.1", |
| - | "--proxy-cacert", |
| - | env.ca.cert_file, |
| + | "--proxy", f"https://{env.proxy_domain}:{pport}/", |
| + | "--resolve", f"{env.proxy_domain}:{pport}:127.0.0.1", |
| + | "--proxy-cacert", env.ca.cert_file, |
| + | "--cacert", env.ca.cert_file, |
| ] | |
| if proxy_proto == "h2": | |
| xargs.append("--proxy-http2") | |
| @@ -151,9 +142,6 @@ def _h2o_proxy_args( | |
| if tunnel: | |
| xargs.append("--proxytunnel") | |
| - | xargs.extend(["--cacert", env.ca.cert_file, "--proxy-insecure"]) |
| - | if insecure: |
| - | xargs.append("--insecure") |
| return xargs | |
| @@ -195,7 +183,7 @@ class TestH3ProxySuccess: | |
| curl = CurlClient(env=env) | |
| url = f"https://localhost:{h2o_server.port}/data.json" | |
| proxy_args = _h2o_proxy_args( | |
| - | env, h2o_proxy, proxy_proto, tunnel=True, insecure=True |
| + | env, h2o_proxy, proxy_proto, tunnel=True |
| ) | |
| r = curl.http_download( | |
| @@ -235,14 +223,14 @@ class TestH3ProxyFailure: | |
| pytest.param( | |
| "h3", | |
| "h2", | |
| - | "connect-udp response status 400", |
| + | "proxy closed connection", |
| marks=MARK_NEEDS_NGHTTP2, | |
| id="fail_h3_over_h2_proxytunnel", | |
| ), | |
| pytest.param( | |
| "h3", | |
| "http/1.1", | |
| - | "connect-udp tunnel failed, response 404", |
| + | "connect-udp tunnel failed", |
| id="fail_h3_over_h1_proxytunnel", | |
| ), | |
| ], | |
| @@ -252,21 +240,24 @@ class TestH3ProxyFailure: | |
| env: Env, | |
| httpd, | |
| nghttpx, | |
| + | nghttpx_fwd, |
| alpn_proto, | |
| proxy_proto, | |
| exp_err, | |
| ): | |
| - | _require_available(httpd=httpd, nghttpx=nghttpx) |
| + | _require_available(httpd=httpd, nghttpx=nghttpx, nghttpx_fwd=nghttpx_fwd) |
| curl = CurlClient(env=env) | |
| - | url = f"https://localhost:{httpd.ports['https']}/data.json" |
| - | proxy_args = _nghttpx_proxy_args(env, nghttpx, proxy_proto, tunnel=True) |
| + | url = f"https://localhost:{env.https_port}/data.json" |
| + | proxy_args = _nghttpx_proxy_args( |
| + | env, nghttpx, nghttpx_fwd, proxy_proto, tunnel=True |
| + | ) |
| r = curl.http_download( | |
| urls=[url], alpn_proto=alpn_proto, with_stats=True, extra_args=proxy_args | |
| ) | |
| - | assert r.exit_code != 0, f"Expected failure but curl succeeded: {r}" |
| + | assert r.exit_code != 0, f"Expected failure but curl succeeded: {r.dump_logs()}" |
| assert exp_err in r.stderr.lower(), ( | |
| - | f"Expected protocol/proxy error but got: {r.stderr}" |
| + | f"Expected protocol/proxy error but got: {r.dump_logs()}" |
| ) | |
| @@ -284,14 +275,16 @@ class TestH3ProxyModeSelection: | |
| ], | |
| ) | |
| def test_60_03_h3_target_auto_connect_udp( | |
| - | self, env: Env, httpd, nghttpx, proxy_proto |
| + | self, env: Env, httpd, nghttpx, nghttpx_fwd, proxy_proto |
| ): | |
| - | _require_available(httpd=httpd, nghttpx=nghttpx) |
| + | _require_available( |
| + | httpd=httpd, nghttpx=nghttpx, nghttpx_fwd=nghttpx_fwd |
| + | ) |
| curl = CurlClient(env=env) | |
| url = f"https://localhost:{httpd.ports['https']}/data.json" | |
| proxy_args = _nghttpx_proxy_args( | |
| - | env, nghttpx, proxy_proto, tunnel=False |
| + | env, nghttpx, nghttpx_fwd, proxy_proto, tunnel=False |
| ) | |
| r = curl.http_download( | |
| urls=[url], alpn_proto="h3", with_stats=True, extra_args=proxy_args | |
| @@ -305,7 +298,7 @@ class TestH3ProxyModeSelection: | |
| "which nghttpx does not support" | |
| ) | |
| assert "connect-udp" in r.stderr.lower(), ( | |
| - | f"expected CONNECT-UDP attempt in output, got: {r.stderr}" |
| + | f"expected CONNECT-UDP attempt in output, got: {r.dump_logs()}" |
| ) | |
| @@ -324,6 +317,9 @@ class TestH3ProxyRuntimeGuards: | |
| @pytest.mark.skipif( | |
| condition=not Env.curl_has_feature("HTTP3"), reason="curl lacks HTTP/3 support" | |
| ) | |
| + | @pytest.mark.skipif( |
| + | condition=Env.curl_has_feature("proxy-HTTP3"), reason="curl has h3 proxy support" |
| + | ) |
| def test_60_04_guard_proxy_http3_unsupported(self, env: Env, httpd): | |
| curl = CurlClient(env=env) | |
| url = f"https://localhost:{httpd.ports['https']}/data.json" | |
| @@ -332,7 +328,6 @@ class TestH3ProxyRuntimeGuards: | |
| "https://127.0.0.1:1/", | |
| "--proxy-http3", | |
| "--proxytunnel", | |
| - | "--proxy-insecure", |
| "--cacert", | |
| env.ca.cert_file, | |
| ] | |
| @@ -340,16 +335,9 @@ class TestH3ProxyRuntimeGuards: | |
| r = curl.http_download( | |
| urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args | |
| ) | |
| - | if not env.curl_has_feature("proxy-HTTP3"): |
| - | r.check_exit_code(2) |
| - | assert UNSUPPORTED_OPT_MSG in r.stderr.lower(), ( |
| - | f"Expected unsupported option failure but got: {r.stderr}" |
| - | ) |
| - | return |
| - | |
| - | r.check_exit_code(1) |
| - | assert NGTCP2_ONLY_MSG in r.stderr.lower(), ( |
| - | f"Expected ngtcp2 guard failure but got: {r.stderr}" |
| + | r.check_exit_code(2) |
| + | assert UNSUPPORTED_OPT_MSG in r.stderr.lower(), ( |
| + | f"Expected unsupported option failure but got: {r.stderr}" |
| ) | |
| @@ -398,26 +386,21 @@ class TestH3ProxyRobustness: | |
| proxy_port = h2o_proxy.port | |
| url = f"https://localhost:{h2o_server.port}/proxy-drop-20m" | |
| out_path = os.path.join(env.gen_dir, "proxy-drop.out") | |
| + | if os.path.exists(out_path): |
| + | os.remove(out_path) |
| args = [ | |
| env.curl, | |
| "--http1.1", | |
| - | "--proxy", |
| - | f"https://{env.proxy_domain}:{proxy_port}/", |
| - | "--resolve", |
| - | f"{env.proxy_domain}:{proxy_port}:127.0.0.1", |
| - | "--proxy-cacert", |
| - | env.ca.cert_file, |
| + | "--proxy", f"https://{env.proxy_domain}:{proxy_port}/", |
| + | "--resolve", f"{env.proxy_domain}:{proxy_port}:127.0.0.1", |
| "--proxy-http3", | |
| "--proxytunnel", | |
| - | "--proxy-insecure", |
| - | "--cacert", |
| - | env.ca.cert_file, |
| - | "--limit-rate", |
| - | "100k", |
| - | "--max-time", |
| - | "20", |
| - | "-o", |
| - | out_path, |
| + | "--proxy-cacert", env.ca.cert_file, |
| + | "--cacert", env.ca.cert_file, |
| + | "--limit-rate", "10k", |
| + | "--max-time", "20", |
| + | "-o", out_path, |
| + | "-v", |
| url, | |
| ] | |
| @@ -426,19 +409,14 @@ class TestH3ProxyRobustness: | |
| proc = subprocess.Popen( | |
| args=args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True | |
| ) | |
| - | time.sleep(1.0) |
| - | assert h2o_proxy.stop(), "failed to stop h2o proxy" |
| + | while not os.path.exists(out_path): |
| + | time.sleep(0.1) |
| + | assert h2o_proxy.kill(), "failed to stop h2o proxy" |
| _, stderr = proc.communicate(timeout=30) | |
| assert proc.returncode != 0, ( | |
| "curl should fail when proxy is terminated mid-transfer" | |
| ) | |
| - | serr = stderr.lower() |
| - | assert ( |
| - | "failed" in serr |
| - | or "transfer closed" in serr |
| - | or "recv failure" in serr |
| - | or "connection" in serr |
| - | ), f"Unexpected error output: {stderr}" |
| + | assert proc.returncode == 56, f'{stderr}' |
| finally: | |
| if proc and (proc.poll() is None): | |
| proc.kill() | |
| @@ -463,7 +441,7 @@ class TestH3ProxyDataTransfer: | |
| _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) | |
| curl = CurlClient(env=env) | |
| url = f"https://localhost:{h2o_server.port}/download-10m" | |
| - | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True, insecure=True) |
| + | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) |
| r = curl.http_download( | |
| urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args | |
| ) | |
| @@ -475,7 +453,7 @@ class TestH3ProxyDataTransfer: | |
| fdata = os.path.join(env.gen_dir, "upload-2m") | |
| curl = CurlClient(env=env) | |
| url = f"https://localhost:{httpd.ports['https']}/curltest/echo?id=[0-0]" | |
| - | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True, insecure=True) |
| + | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) |
| r = curl.http_upload( | |
| urls=[url], | |
| data=f"@{fdata}", | |
| @@ -490,7 +468,7 @@ class TestH3ProxyDataTransfer: | |
| count = 5 | |
| curl = CurlClient(env=env) | |
| urln = f"https://localhost:{h2o_server.port}/download-1m?[0-{count - 1}]" | |
| - | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True, insecure=True) |
| + | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) |
| proxy_args.extend(["--parallel", "--parallel-max", f"{count}"]) | |
| r = curl.http_download( | |
| urls=[urln], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args | |
| @@ -507,7 +485,7 @@ class TestH3ProxyConnectionManagement: | |
| _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) | |
| curl = CurlClient(env=env) | |
| url = f"https://localhost:{h2o_server.port}/data.json" | |
| - | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True, insecure=True) |
| + | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) |
| proxy_args.extend(["--proxy-user", "testuser:testpass"]) | |
| r = curl.http_download( | |
| urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args | |
| @@ -519,7 +497,7 @@ class TestH3ProxyConnectionManagement: | |
| _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) | |
| curl = CurlClient(env=env) | |
| urln = f"https://localhost:{h2o_server.port}/data.json?[0-2]" | |
| - | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True, insecure=True) |
| + | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) |
| r = curl.http_download( | |
| urls=[urln], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args | |
| ) | |
| @@ -528,30 +506,29 @@ class TestH3ProxyConnectionManagement: | |
| f"expected proxy connection reuse, got {r.total_connects} connects" | |
| ) | |
| + | @pytest.mark.skipif(condition=not Env.curl_has_feature('SSLS-EXPORT'), |
| + | reason='curl lacks SSL session export support') |
| def test_60_12_quic_session_resumption(self, env: Env, h2o_server, h2o_proxy): | |
| _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) | |
| - | # First request establishes QUIC session |
| - | curl1 = CurlClient(env=env) |
| + | curl = CurlClient(env=env) |
| url = f"https://localhost:{h2o_server.port}/data.json" | |
| - | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True, insecure=True) |
| - | r1 = curl1.http_download( |
| - | urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args |
| + | xargs = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) |
| + | session_file = os.path.join(env.gen_dir, 'test_60_12.sessions') |
| + | if os.path.exists(session_file): |
| + | os.remove(session_file) |
| + | xargs.extend(['--ssl-sessions', session_file]) |
| + | # First request establishes QUIC session |
| + | r1 = curl.http_download( |
| + | urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=xargs |
| ) | |
| r1.check_response(count=1, http_status=200) | |
| - | # Second request from a fresh CurlClient; session may be reused |
| - | # by the TLS session cache if supported |
| - | curl2 = CurlClient(env=env) |
| - | r2 = curl2.http_download( |
| - | urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args |
| + | xargs.extend(['--trace-config', 'ssls']) |
| + | r2 = curl.http_download( |
| + | urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=xargs |
| ) | |
| r2.check_response(count=1, http_status=200) | |
| - | # Third request from a fresh CurlClient; session may be reused |
| - | # by the TLS session cache if supported |
| - | curl3 = CurlClient(env=env) |
| - | r3 = curl3.http_download( |
| - | urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args |
| - | ) |
| - | r3.check_response(count=1, http_status=200) |
| + | reuses = [line for line in r2.trace_lines if '[SSLS] took session for proxy.http.curl.se' in line] |
| + | assert len(reuses), f'{r2.dump_logs()}' |
| class TestH3ProxyUdpTunnel: | |
| @@ -582,7 +559,7 @@ class TestH3ProxyUdpTunnel: | |
| _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) | |
| curl = CurlClient(env=env) | |
| url = f"https://localhost:{h2o_server.port}/{fname}" | |
| - | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True, insecure=True) |
| + | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) |
| r = curl.http_download( | |
| urls=[url], alpn_proto="h3", with_stats=True, extra_args=proxy_args | |
| ) | |
| @@ -590,11 +567,17 @@ class TestH3ProxyUdpTunnel: | |
| _check_download_size(curl, fsize) | |
| @MARK_NEEDS_NGHTTPX | |
| - | def test_60_14_udp_tunnel_capsule_absent(self, env: Env, httpd, nghttpx): |
| - | _require_available(httpd=httpd, nghttpx=nghttpx) |
| + | def test_60_14_udp_tunnel_capsule_absent( |
| + | self, env: Env, httpd, nghttpx, nghttpx_fwd |
| + | ): |
| + | _require_available( |
| + | httpd=httpd, nghttpx=nghttpx, nghttps_fwd=nghttpx_fwd |
| + | ) |
| curl = CurlClient(env=env) | |
| url = f"https://localhost:{httpd.ports['https']}/data.json" | |
| - | proxy_args = _nghttpx_proxy_args(env, nghttpx, "h3", tunnel=True) |
| + | proxy_args = _nghttpx_proxy_args( |
| + | env, nghttpx, nghttpx_fwd, "h3", tunnel=True |
| + | ) |
| r = curl.http_download( | |
| urls=[url], alpn_proto="h3", with_stats=True, extra_args=proxy_args | |
| ) | |
| @@ -608,25 +591,21 @@ class TestH3ProxyEdgeCases: | |
| pytestmark = H3_PROXY_COMMON_MARKS + [MARK_NEEDS_H2O] | |
| - | def test_60_15_connect_timeout(self, env: Env, h2o_server): |
| - | _require_available(h2o_server=h2o_server) |
| + | def test_60_15_connect_timeout(self, env: Env, h2o_proxy): |
| + | _require_available(h2o_proxy=h2o_proxy) |
| curl = CurlClient(env=env, timeout=15) | |
| - | url = f"https://localhost:{h2o_server.port}/data.json" |
| - | proxy_args = [ |
| - | "--proxy", |
| - | "https://192.0.2.1:1/", |
| - | "--proxy-http3", |
| - | "--proxytunnel", |
| - | "--proxy-insecure", |
| - | "--connect-timeout", |
| - | "3", |
| - | "--cacert", |
| - | env.ca.cert_file, |
| + | url = f"https://localhost:{h2o_proxy.port}/data.json" |
| + | # ipv6 0100::/64 is supposed to go into the void (rfc6666) |
| + | xargs = [ |
| + | '--proxy', 'https://xxx.invalid/', |
| + | '--resolve', 'xxx.invalid:443:0100::1,0100::2,0100::3', |
| + | '--proxy-http3', '--proxytunnel', |
| + | '--connect-timeout', '1', |
| ] | |
| r = curl.http_download( | |
| - | urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args |
| + | urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=xargs |
| ) | |
| - | assert r.exit_code != 0, "expected timeout connecting to unreachable proxy" |
| + | r.check_exit_code(28) # CURLE_OPERATION_TIMEDOUT |
| assert r.duration.total_seconds() < 10, ( | |
| f"timeout not respected: took {r.duration.total_seconds():.1f}s" | |
| ) | |
| @@ -635,8 +614,8 @@ class TestH3ProxyEdgeCases: | |
| def test_60_16_h2_uses_connect_tcp_not_udp(self, env: Env, httpd, h2o_proxy): | |
| _require_available(httpd=httpd, h2o_proxy=h2o_proxy) | |
| curl = CurlClient(env=env) | |
| - | url = f"https://localhost:{httpd.ports['https']}/data.json" |
| - | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True, insecure=True) |
| + | url = f"https://localhost:{env.https_port}/data.json" |
| + | proxy_args = curl.get_proxy_args("h3", tunnel=True) |
| # h2 inner traffic always uses CONNECT (TCP), never CONNECT-UDP, | |
| # even through an HTTP/3 proxy with --proxytunnel. h2o supports | |
| # CONNECT TCP tunneling, so this request succeeds. | |
| @@ -663,7 +642,7 @@ class TestH3ProxyHappyEyeballs: | |
| _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) | |
| curl = CurlClient(env=env, run_env={"CURL_DEBUG": "HAPPY-EYEBALLS,H3-PROXY"}) | |
| url = f"https://localhost:{h2o_server.port}/data.json" | |
| - | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True, insecure=True) |
| + | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) |
| r = curl.http_download( | |
| urls=[url], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args | |
| ) | |
| @@ -679,9 +658,7 @@ class TestH3ProxyHappyEyeballs: | |
| for alpn_proto in ["http/1.1", "h2", "h3"]: | |
| curl = CurlClient(env=env) | |
| url = f"https://localhost:{h2o_server.port}/data.json" | |
| - | proxy_args = _h2o_proxy_args( |
| - | env, h2o_proxy, "h3", tunnel=True, insecure=True |
| - | ) |
| + | proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) |
| proxy_args.append("--ipv4") | |
| r = curl.http_download( | |
| urls=[url], | |
| @@ -684,12 +684,13 @@ class CurlClient: | |
| def get_proxy_args(self, proto: str = 'http/1.1', | |
| proxys: bool = True, tunnel: bool = False, | |
| - | use_ip: bool = False, use_ipv6: bool = False): |
| + | use_ip: bool = False, use_ipv6: bool = False, |
| + | use_h2o: bool = False): |
| proxy_name = '[::1]' if use_ipv6 else \ | |
| self._server_addr if use_ip else self.env.proxy_domain | |
| if proxys: | |
| if tunnel: | |
| - | pport = self.env.pts_port(proto) |
| + | pport = self.env.pts_port(proto, use_h2o=use_h2o) |
| elif proto == 'h3': | |
| pport = self.env.h3proxys_port | |
| else: | |
| @@ -824,15 +824,16 @@ class Env: | |
| @property | |
| def h3proxys_port(self) -> int: | |
| - | return self.CONFIG.ports["h3proxys"] |
| + | return self.CONFIG.ports["h2o_h3proxys"] |
| - | def pts_port(self, proto: str = "http/1.1") -> int: |
| + | def pts_port(self, proto: str = "http/1.1", use_h2o: bool = False) -> int: |
| # proxy tunnel port | |
| + | prefix = 'h2o_' if use_h2o else '' |
| if proto == "h3": | |
| - | return self.CONFIG.ports["h3proxys"] |
| + | return self.CONFIG.ports.get("h2o_h3proxys", 0) |
| if proto == "h2": | |
| - | return self.CONFIG.ports["h2proxys"] |
| - | return self.CONFIG.ports["proxys"] |
| + | return self.CONFIG.ports.get(f"{prefix}h2proxys", 0) |
| + | return self.CONFIG.ports[f"{prefix}proxys"] |
| @property | |
| def caddy(self) -> str: | |
| @@ -160,6 +160,12 @@ class H2o: | |
| ) | |
| return True | |
| + | def kill(self, wait_dead=True): |
| + | if self._process: |
| + | self._process.kill() |
| + | return True |
| + | return False |
| + | |
| def restart(self): | |
| self.stop() | |
| return self.start() | |
| @@ -317,9 +323,9 @@ class H2oProxy(H2o): | |
| super().initial_start() | |
| def startup(ports: Dict[str, int]) -> bool: | |
| - | self._port = ports["h3proxys"] |
| - | self._h2_port = ports["h2proxys"] |
| - | self._h1_port = ports["proxys"] |
| + | self._port = ports["h2o_h3proxys"] |
| + | self._h2_port = ports["h2o_h2proxys"] |
| + | self._h1_port = ports["h2o_proxys"] |
| if self.start(): | |
| self.env.update_ports(ports) | |
| return True | |
| @@ -331,9 +337,9 @@ class H2oProxy(H2o): | |
| return alloc_ports_and_do( | |
| { | |
| - | "h3proxys": socket.SOCK_DGRAM, |
| - | "h2proxys": socket.SOCK_STREAM, |
| - | "proxys": socket.SOCK_STREAM, |
| + | "h2o_h3proxys": socket.SOCK_DGRAM, |
| + | "h2o_h2proxys": socket.SOCK_STREAM, |
| + | "h2o_proxys": socket.SOCK_STREAM, |
| }, | |
| startup, | |
| self.env.gen_root, | |
| @@ -47,7 +47,7 @@ class Nghttpx: | |
| self._name = name | |
| self._domain = domain | |
| self._port = 0 | |
| - | self._https_port = 0 |
| + | self._port_is_quic = False |
| self._cmd = env.nghttpx | |
| self._run_dir = os.path.join(env.gen_dir, name) | |
| self._pid_file = os.path.join(self._run_dir, 'nghttpx.pid') | |
| @@ -76,8 +76,12 @@ class Nghttpx: | |
| return self.reload() | |
| @property | |
| - | def https_port(self): |
| - | return self._https_port |
| + | def port(self): |
| + | return self._port |
| + | |
| + | @property |
| + | def port_is_quic(self): |
| + | return self._port_is_quic |
| def exists(self): | |
| return self._cmd and os.path.exists(self._cmd) | |
| @@ -150,18 +154,14 @@ class Nghttpx: | |
| curl = CurlClient(env=self.env, run_dir=self._tmp_dir) | |
| try_until = datetime.now() + timeout | |
| while datetime.now() < try_until: | |
| - | if self._https_port > 0: |
| - | check_url = f'https://{self._domain}:{self._port}/' |
| - | r = curl.http_get(url=check_url, extra_args=[ |
| - | '--trace', 'curl.trace', '--trace-time', |
| - | '--connect-timeout', '1' |
| - | ]) |
| - | else: |
| - | check_url = f'https://{self._domain}:{self._port}/' |
| - | r = curl.http_get(url=check_url, extra_args=[ |
| - | '--trace', 'curl.trace', '--trace-time', |
| - | '--http3-only', '--connect-timeout', '1' |
| - | ]) |
| + | xargs = [ |
| + | '--trace', 'curl.trace', '--trace-time', |
| + | '--connect-timeout', '1' |
| + | ] |
| + | if self.port_is_quic: |
| + | xargs.extend(['--http3-only']) |
| + | check_url = f'https://{self._domain}:{self.port}/' |
| + | r = curl.http_get(url=check_url, extra_args=xargs) |
| if r.exit_code != 0: | |
| return True | |
| log.debug(f'waiting for nghttpx to stop responding: {r}') | |
| @@ -173,18 +173,14 @@ class Nghttpx: | |
| curl = CurlClient(env=self.env, run_dir=self._tmp_dir) | |
| try_until = datetime.now() + timeout | |
| while datetime.now() < try_until: | |
| - | if self._https_port > 0: |
| - | check_url = f'https://{self._domain}:{self._port}/' |
| - | r = curl.http_get(url=check_url, extra_args=[ |
| - | '--trace', 'curl.trace', '--trace-time', |
| - | '--connect-timeout', '1' |
| - | ]) |
| - | else: |
| - | check_url = f'https://{self._domain}:{self._port}/' |
| - | r = curl.http_get(url=check_url, extra_args=[ |
| - | '--http3-only', '--trace', 'curl.trace', '--trace-time', |
| - | '--connect-timeout', '1' |
| - | ]) |
| + | xargs = [ |
| + | '--trace', 'curl.trace', '--trace-time', |
| + | '--connect-timeout', '1' |
| + | ] |
| + | if self.port_is_quic: |
| + | xargs.extend(['--http3-only']) |
| + | check_url = f'https://{self._domain}:{self.port}/' |
| + | r = curl.http_get(url=check_url, extra_args=xargs) |
| if r.exit_code == 0: | |
| return True | |
| time.sleep(.1) | |
| @@ -216,13 +212,18 @@ class NghttpxQuic(Nghttpx): | |
| def __init__(self, env: Env): | |
| super().__init__(env=env, name='nghttpx-quic', | |
| domain=env.domain1, cred_name=env.domain1) | |
| - | self._https_port = env.https_port |
| + | self._https_port = 0 |
| def initial_start(self): | |
| super().initial_start() | |
| def startup(ports: Dict[str, int]) -> bool: | |
| - | self._port = ports['nghttpx_https'] |
| + | self._https_port = ports['nghttpx_https'] |
| + | if self.supports_h3(): |
| + | self._port = self.env.h3_port |
| + | self._port_is_quic = True |
| + | else: |
| + | self._port = self._https_port |
| if self.start(): | |
| self.env.update_ports(ports) | |
| return True | |
| @@ -240,10 +241,10 @@ class NghttpxQuic(Nghttpx): | |
| creds = self.env.get_credentials(self._cred_name) | |
| assert creds # convince pytype this is not None | |
| self._loaded_cred_name = self._cred_name | |
| - | args = [self._cmd, f'--frontend=*,{self._port};tls'] |
| + | args = [self._cmd, f'--frontend=*,{self._https_port};tls'] |
| if self.supports_h3(): | |
| args.extend([ | |
| - | f'--frontend=*,{self.env.h3_port};quic', |
| + | f'--frontend=*,{self._port};quic', |
| '--frontend-quic-early-data', | |
| ]) | |
| args.extend([ | |
| @@ -113,7 +113,8 @@ static int test_idx; | |
| struct cf_test_ctx { | |
| int idx; | |
| int ai_family; | |
| - | uint8_t transport; |
| + | uint8_t transport_in; |
| + | uint8_t transport_out; |
| char id[16]; | |
| struct curltime started; | |
| timediff_t fail_delay_ms; | |
| @@ -167,7 +168,8 @@ static CURLcode cf_test_create(struct Curl_cfilter **pcf, | |
| struct Curl_easy *data, | |
| struct connectdata *conn, | |
| struct Curl_sockaddr_ex *addr, | |
| - | uint8_t transport) |
| + | uint8_t transport_in, |
| + | uint8_t transport_out) |
| { | |
| static const struct Curl_cftype cft_test = { | |
| "TEST", | |
| @@ -201,7 +203,8 @@ static CURLcode cf_test_create(struct Curl_cfilter **pcf, | |
| } | |
| ctx->idx = test_idx++; | |
| ctx->ai_family = addr->family; | |
| - | ctx->transport = transport; |
| + | ctx->transport_in = transport_in; |
| + | ctx->transport_out = transport_out; |
| ctx->started = curlx_now(); | |
| current_tr->ongoing++; | |
| if(current_tr->ongoing > current_tr->max_concurrent) | |