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 */
#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). */
break;
}
else if(nread == 0) {
+ CURL_TRC_CF(data, cf, "server closed connection");
ctx->conn_closed = TRUE;
break;
}
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:
#include "sendf.h"
#include "multiif.h"
#include "cfilters.h"
+#include "cf-capsule.h"
#include "cf-socket.h"
#include "connect.h"
#include "progress.h"
}
*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);
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,
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
#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"
struct transport_provider {
- uint8_t transport;
cf_ip_connect_create *cf_create;
+ uint8_t transport;
+ bool tunnel_proxy;
};
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;
for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) {
if(transport == transport_providers[i].transport) {
transport_providers[i].cf_create = cf_create;
- return;
}
}
}
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 */
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;
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;
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,
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 */
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
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);
} 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);
};
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);
}
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,
*/
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;
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);
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;
/* 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 */
struct Curl_addrinfo;
struct Curl_cfilter;
struct Curl_easy;
+struct Curl_peer;
struct Curl_sockaddr_ex;
/**
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)
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;
(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;
goto out;
}
- result = cf_socket_ctx_init(ctx, addr, transport);
+ result = cf_socket_ctx_init(ctx, addr, transport_out);
if(result)
goto out;
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;
(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;
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;
(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;
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
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
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.
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;
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;
}
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;
}
}
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;
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);
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;
}
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)
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 */
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;
#define IS_QUIC_PROXY(t) ((t) == CURLPROXY_HTTPS3)
+uint8_t Curl_http_proxy_transport(uint8_t proxytype);
+
#endif /* HEADER_CURL_HTTP_PROXY_H */
#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]) {
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;
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;
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)
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;
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
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
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
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")
if tunnel:
xargs.append("--proxytunnel")
- xargs.extend(["--cacert", env.ca.cert_file, "--proxy-insecure"])
- if insecure:
- xargs.append("--insecure")
return xargs
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(
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",
),
],
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()}"
)
],
)
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
"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()}"
)
@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"
"https://127.0.0.1:1/",
"--proxy-http3",
"--proxytunnel",
- "--proxy-insecure",
"--cacert",
env.ca.cert_file,
]
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}"
)
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,
]
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()
_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
)
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}",
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
_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
_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
)
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:
_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
)
_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
)
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"
)
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.
_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
)
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],
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:
@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:
)
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()
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
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,
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')
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)
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}')
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)
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
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([
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;
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",
}
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)