mirror of
https://github.com/curl/curl.git
synced 2026-06-02 03:44:14 +03:00
h3-proxy: fixes around H3 proxy
code: - less exception handling in existing code - true ip happy eyeballing - enable certificate verification - cf-h2-proxy: abort connection when server closed connection tests: - remove all --insecure and --proxy-insecure args - make session reuse test_60_12 a working one - resolve port conflicts between h2o and nghttpx - use proxy args better - make test_60_06 run shorter - kill h2o at the end of tests, normal stop takes too long Ref:59213f8248#21789 Follow-up toe78b1b3ecc#21153 Closes #21798
This commit is contained in:
parent
59213f8248
commit
e4139a73c8
25 changed files with 442 additions and 365 deletions
|
|
@ -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);
|
||||
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;
|
||||
}
|
||||
|
||||
#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);
|
||||
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;
|
||||
|
|
|
|||
145
lib/connect.c
145
lib/connect.c
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue