mirror of
https://github.com/curl/curl.git
synced 2026-08-25 07:33:36 +03:00
HTTP/3: add proxy CONNECT and MASQUE CONNECT-UDP support (ngtcp2 QUIC)
This patch adds two major proxy capabilities to curl (ngtcp2 QUIC):
- HTTP/3 Proxy CONNECT: Tunnel HTTP/1.1 or HTTP/2 traffic through an
HTTPS proxy that speaks HTTP/3 (QUIC) using the standard CONNECT
method over an HTTP/3 connection.
- MASQUE CONNECT-UDP: Tunnel HTTP/3 (QUIC) traffic through an HTTP
proxy (speaking HTTP/1.1, HTTP/2, or HTTP/3) using the extended
CONNECT method with the CONNECT-UDP protocol (RFC9297 & RFC9298).
Public API additions:
- `CURLPROXY_HTTPS3`: new proxy type constant for HTTP/3 proxy
- `--proxy-http3`: new CLI flag to negotiate HTTP/3 with HTTPS proxy
The implementation adds two new filters:
- `H3-PROXY` - enables negotiating HTTP/3 (QUIC) to the proxy and
running CONNECT/CONNECT-UDP through that proxy transport.
- `CAPSULE` - dedicated filter inserted between QUIC transport and
HTTP-PROXY to handle datagram capsule encapsulation/decapsulation.
Here is how the curl filter chaining looks in different scenarios:
- HTTP/3 Proxy CONNECT (tunneling TCP protocols over QUIC proxy):
conn -> HTTP/1.1 or HTTP/2 -> SSL -> HTTP-PROXY ->
H3-PROXY -> HAPPY-EYEBALLS -> UDP
- MASQUE CONNECT-UDP (tunneling QUIC over any proxy):
conn -> HTTP/3 -> CAPSULE -> HTTP-PROXY -> H3-PROXY ->
HAPPY-EYEBALLS -> UDP
conn -> HTTP/3 -> CAPSULE -> HTTP-PROXY -> H1-PROXY or H2-PROXY ->
SSL -> HAPPY-EYEBALLS -> TCP
- Both features currently require the ngtcp2 QUIC backend.
- Both features are experimental (disabled by default). Enable with
`--enable-proxy-http3`(autotools) or `-DUSE_PROXY_HTTP3=ON`(CMake).
Tests:
- tests/unit/unit3400.c: Unit tests for capsule protocol encode/decode
- tests/http/test_60_h3_proxy.py: Comprehensive pytest integration suite
- tests/http/testenv/h2o.py: Managing h2o instances with HTTP/1.1, HTTP/2,
and HTTP/3 (QUIC) listeners, proxy.connect and proxy.connect-udp enabled.
References:
RFC 9297 - HTTP Datagrams and the Capsule Protocol
RFC 9298 - Proxying UDP in HTTP
RFC 9000 §16 — Variable-Length Integer Encoding
Signed-off-by: Aritra Basu <aritrbas+gh@cisco.com>
Closes #21153
This commit is contained in:
parent
efc3f2309e
commit
e78b1b3ecc
66 changed files with 7401 additions and 473 deletions
|
|
@ -72,6 +72,7 @@
|
|||
|
||||
#define QUIC_MAX_STREAMS (256 * 1024)
|
||||
#define QUIC_HANDSHAKE_TIMEOUT (10 * NGTCP2_SECONDS)
|
||||
#define QUIC_TUNNEL_INBUF_SIZE (64 * 1024)
|
||||
|
||||
/* We announce a small window size in transport param to the server,
|
||||
* and grow that immediately to max when no rate limit is in place.
|
||||
|
|
@ -95,6 +96,7 @@
|
|||
#define H3_STREAM_SEND_BUFFER_MAX (10 * 1024 * 1024)
|
||||
#define H3_STREAM_SEND_CHUNKS \
|
||||
(H3_STREAM_SEND_BUFFER_MAX / H3_STREAM_CHUNK_SIZE)
|
||||
#define QUIC_TUNNEL_INGRESS_PKT_LIMIT 1000
|
||||
|
||||
/*
|
||||
* Store ngtcp2 version info in this buffer.
|
||||
|
|
@ -139,6 +141,8 @@ struct cf_ngtcp2_ctx {
|
|||
is accepted by peer */
|
||||
CURLcode tls_vrfy_result; /* result of TLS peer verification */
|
||||
int qlogfd;
|
||||
unsigned char *tunnel_inbuf; /* ingress buffer for tunneled packets */
|
||||
size_t tunnel_inbuf_len;
|
||||
BIT(initialized);
|
||||
BIT(tls_handshake_complete); /* TLS handshake is done */
|
||||
BIT(use_earlydata); /* Using 0RTT data */
|
||||
|
|
@ -156,6 +160,8 @@ static void cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx)
|
|||
{
|
||||
DEBUGASSERT(!ctx->initialized);
|
||||
ctx->qlogfd = -1;
|
||||
ctx->tunnel_inbuf = NULL;
|
||||
ctx->tunnel_inbuf_len = 0;
|
||||
ctx->version = NGTCP2_PROTO_VER_MAX;
|
||||
Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
|
||||
H3_STREAM_POOL_SPARES);
|
||||
|
|
@ -173,6 +179,8 @@ static void cf_ngtcp2_ctx_free(struct cf_ngtcp2_ctx *ctx)
|
|||
curlx_dyn_free(&ctx->scratch);
|
||||
Curl_uint32_hash_destroy(&ctx->streams);
|
||||
Curl_ssl_peer_cleanup(&ctx->peer);
|
||||
curlx_safefree(ctx->tunnel_inbuf);
|
||||
ctx->tunnel_inbuf_len = 0;
|
||||
}
|
||||
curlx_free(ctx);
|
||||
}
|
||||
|
|
@ -493,7 +501,7 @@ static void quic_settings(struct cf_ngtcp2_ctx *ctx,
|
|||
static CURLcode init_ngh3_conn(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data);
|
||||
|
||||
static int cf_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data)
|
||||
static int cb_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data)
|
||||
{
|
||||
struct Curl_cfilter *cf = user_data;
|
||||
struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL;
|
||||
|
|
@ -863,7 +871,7 @@ static ngtcp2_callbacks ng_callbacks = {
|
|||
ngtcp2_crypto_client_initial_cb,
|
||||
NULL, /* recv_client_initial */
|
||||
ngtcp2_crypto_recv_crypto_data_cb,
|
||||
cf_ngtcp2_handshake_completed,
|
||||
cb_ngtcp2_handshake_completed,
|
||||
NULL, /* recv_version_negotiation */
|
||||
ngtcp2_crypto_encrypt_cb,
|
||||
ngtcp2_crypto_decrypt_cb,
|
||||
|
|
@ -982,6 +990,11 @@ static CURLcode cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf,
|
|||
if(!ctx->qconn)
|
||||
return CURLE_OK;
|
||||
|
||||
if(ctx->q.sockfd == CURL_SOCKET_BAD) {
|
||||
/* Tunneled QUIC, no direct socket - delegate to next filter */
|
||||
return cf->next->cft->adjust_pollset(cf->next, data, ps);
|
||||
}
|
||||
|
||||
Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send);
|
||||
if(!want_send && !Curl_bufq_is_empty(&ctx->q.sendbuf))
|
||||
want_send = TRUE;
|
||||
|
|
@ -1904,8 +1917,72 @@ static CURLcode cf_progress_ingress(struct Curl_cfilter *cf,
|
|||
|
||||
rctx.pktx = pktx;
|
||||
rctx.pkt_count = 0;
|
||||
return vquic_recv_packets(cf, data, &ctx->q, 1000,
|
||||
|
||||
if(ctx->q.sockfd != CURL_SOCKET_BAD) {
|
||||
/* Direct UDP socket (via happy eyeballs) */
|
||||
return vquic_recv_packets(cf, data, &ctx->q, 1000,
|
||||
cf_ngtcp2_recv_pkts, &rctx);
|
||||
}
|
||||
else {
|
||||
/* Tunneled QUIC (CONNECT-UDP through proxy) */
|
||||
unsigned char *buf;
|
||||
size_t max_udp_payload = QUIC_TUNNEL_INBUF_SIZE;
|
||||
size_t pkt_limit = QUIC_TUNNEL_INGRESS_PKT_LIMIT;
|
||||
size_t nread;
|
||||
struct sockaddr_storage remote_addr;
|
||||
socklen_t remote_addrlen;
|
||||
|
||||
if(ctx->qconn) {
|
||||
size_t max_path_payload;
|
||||
max_path_payload =
|
||||
ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn);
|
||||
if(max_path_payload > max_udp_payload)
|
||||
max_udp_payload = max_path_payload;
|
||||
}
|
||||
|
||||
if(ctx->tunnel_inbuf_len < max_udp_payload) {
|
||||
unsigned char *newbuf =
|
||||
(unsigned char *)curlx_realloc(ctx->tunnel_inbuf, max_udp_payload);
|
||||
if(!newbuf)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
ctx->tunnel_inbuf = newbuf;
|
||||
ctx->tunnel_inbuf_len = max_udp_payload;
|
||||
}
|
||||
buf = ctx->tunnel_inbuf;
|
||||
|
||||
while(pkt_limit--) {
|
||||
result = Curl_conn_cf_recv(cf->next, data, (char *)buf,
|
||||
ctx->tunnel_inbuf_len, &nread);
|
||||
if(result == CURLE_AGAIN) {
|
||||
/* no more data available at the moment */
|
||||
return CURLE_OK;
|
||||
}
|
||||
if(result) {
|
||||
CURL_TRC_CF(data, cf, "ingress, recv from tunnel failed: %d",
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
if(nread == 0) {
|
||||
/* tunnel closed */
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
memcpy(&remote_addr, ctx->connected_path.remote.addr,
|
||||
ctx->connected_path.remote.addrlen);
|
||||
remote_addrlen = (socklen_t)ctx->connected_path.remote.addrlen;
|
||||
result = cf_ngtcp2_recv_pkts(buf, nread, nread, &remote_addr,
|
||||
remote_addrlen, 0, &rctx);
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
if(!ctx->q.got_first_byte) {
|
||||
ctx->q.got_first_byte = TRUE;
|
||||
ctx->q.first_byte_at = ctx->q.last_op;
|
||||
}
|
||||
ctx->q.last_io = ctx->q.last_op;
|
||||
}
|
||||
return CURLE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2189,6 +2266,7 @@ static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx)
|
|||
}
|
||||
ctx->qlogfd = -1;
|
||||
Curl_vquic_tls_cleanup(&ctx->tls);
|
||||
Curl_ssl_peer_cleanup(&ctx->peer);
|
||||
vquic_ctx_free(&ctx->q);
|
||||
if(ctx->h3conn) {
|
||||
nghttp3_conn_del(ctx->h3conn);
|
||||
|
|
@ -2220,6 +2298,12 @@ static CURLcode cf_ngtcp2_shutdown(struct Curl_cfilter *cf,
|
|||
return CURLE_OK;
|
||||
}
|
||||
|
||||
if(!cf->next) {
|
||||
Curl_bufq_reset(&ctx->q.sendbuf);
|
||||
*done = TRUE;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
CF_DATA_SAVE(save, cf, data);
|
||||
*done = FALSE;
|
||||
pktx_init(&pktx, cf, data);
|
||||
|
|
@ -2648,30 +2732,81 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
|
|||
if(result)
|
||||
return result;
|
||||
|
||||
if(Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL))
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
ctx->q.local_addrlen = sizeof(ctx->q.local_addr);
|
||||
rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr,
|
||||
&ctx->q.local_addrlen);
|
||||
if(rv == -1)
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
/* Query socket and remote address from sub-chain */
|
||||
if(Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL)) {
|
||||
/* No direct socket - must be tunneled QUIC (CONNECT-UDP through proxy) */
|
||||
ctx->q.sockfd = CURL_SOCKET_BAD;
|
||||
}
|
||||
|
||||
ngtcp2_addr_init(&ctx->connected_path.local,
|
||||
(struct sockaddr *)&ctx->q.local_addr,
|
||||
ctx->q.local_addrlen);
|
||||
ngtcp2_addr_init(&ctx->connected_path.remote,
|
||||
&sockaddr->curl_sa_addr, (socklen_t)sockaddr->addrlen);
|
||||
if(ctx->q.sockfd != CURL_SOCKET_BAD) {
|
||||
/* Direct UDP socket - get local address for ngtcp2 */
|
||||
ctx->q.local_addrlen = sizeof(ctx->q.local_addr);
|
||||
rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr,
|
||||
&ctx->q.local_addrlen);
|
||||
if(rv == -1)
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
|
||||
rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid,
|
||||
&ctx->connected_path,
|
||||
NGTCP2_PROTO_VER_V1, &ng_callbacks,
|
||||
&ctx->settings, &ctx->transport_params,
|
||||
Curl_ngtcp2_mem(), cf);
|
||||
if(rc)
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
ngtcp2_addr_init(&ctx->connected_path.local,
|
||||
(struct sockaddr *)&ctx->q.local_addr,
|
||||
ctx->q.local_addrlen);
|
||||
ngtcp2_addr_init(&ctx->connected_path.remote,
|
||||
&sockaddr->curl_sa_addr, (socklen_t)sockaddr->addrlen);
|
||||
|
||||
ctx->conn_ref.get_conn = get_conn;
|
||||
ctx->conn_ref.user_data = cf;
|
||||
rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid,
|
||||
&ctx->connected_path,
|
||||
NGTCP2_PROTO_VER_V1, &ng_callbacks,
|
||||
&ctx->settings, &ctx->transport_params,
|
||||
Curl_ngtcp2_mem(), cf);
|
||||
if(rc)
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
|
||||
ctx->conn_ref.get_conn = get_conn;
|
||||
ctx->conn_ref.user_data = cf;
|
||||
}
|
||||
else {
|
||||
/* Tunneled QUIC (e.g. CONNECT-UDP): get remote address
|
||||
from the connected filter below */
|
||||
const struct Curl_sockaddr_ex *remote = NULL;
|
||||
if(cf->next->cft->query(cf->next, data, CF_QUERY_REMOTE_ADDR, NULL,
|
||||
CURL_UNCONST(&remote)))
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
if(!remote)
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
|
||||
memset(&ctx->q.local_addr, 0, sizeof(ctx->q.local_addr));
|
||||
switch(remote->family) {
|
||||
case AF_INET:
|
||||
((struct sockaddr_in *)&ctx->q.local_addr)->sin_family = AF_INET;
|
||||
ctx->q.local_addrlen = sizeof(struct sockaddr_in);
|
||||
break;
|
||||
#ifdef USE_IPV6
|
||||
case AF_INET6:
|
||||
((struct sockaddr_in6 *)&ctx->q.local_addr)->sin6_family = AF_INET6;
|
||||
ctx->q.local_addrlen = sizeof(struct sockaddr_in6);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
ngtcp2_addr_init(&ctx->connected_path.local,
|
||||
(struct sockaddr *)&ctx->q.local_addr,
|
||||
ctx->q.local_addrlen);
|
||||
ngtcp2_addr_init(&ctx->connected_path.remote,
|
||||
&remote->curl_sa_addr,
|
||||
(socklen_t)remote->addrlen);
|
||||
|
||||
rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid,
|
||||
&ctx->connected_path,
|
||||
NGTCP2_PROTO_VER_V1, &ng_callbacks,
|
||||
&ctx->settings, &ctx->transport_params,
|
||||
Curl_ngtcp2_mem(), cf);
|
||||
if(rc)
|
||||
return CURLE_QUIC_CONNECT_ERROR;
|
||||
|
||||
ctx->conn_ref.get_conn = get_conn;
|
||||
ctx->conn_ref.user_data = cf;
|
||||
}
|
||||
|
||||
result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, &ALPN_SPEC_H3,
|
||||
cf_ngtcp2_tls_ctx_setup, &ctx->tls,
|
||||
|
|
@ -2720,8 +2855,8 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf,
|
|||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/* Connect the UDP filter first */
|
||||
if(!cf->next->connected) {
|
||||
/* Connect the sub-chain */
|
||||
if(cf->next && !cf->next->connected) {
|
||||
result = Curl_conn_cf_connect(cf->next, data, done);
|
||||
if(result || !*done)
|
||||
return result;
|
||||
|
|
@ -2803,11 +2938,14 @@ out:
|
|||
|
||||
#ifdef CURLVERBOSE
|
||||
if(result) {
|
||||
struct ip_quadruple ip;
|
||||
if(ctx->q.sockfd != CURL_SOCKET_BAD) {
|
||||
/* Direct UDP socket - get IP info for error reporting */
|
||||
struct ip_quadruple ip;
|
||||
|
||||
if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip))
|
||||
infof(data, "QUIC connect to %s port %u failed: %s",
|
||||
ip.remote_ip, ip.remote_port, curl_easy_strerror(result));
|
||||
if(!Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip))
|
||||
infof(data, "QUIC connect to %s port %u failed: %s",
|
||||
ip.remote_ip, ip.remote_port, curl_easy_strerror(result));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if(!result && ctx->qconn) {
|
||||
|
|
@ -3003,4 +3141,33 @@ out:
|
|||
return result;
|
||||
}
|
||||
|
||||
CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at)
|
||||
{
|
||||
struct cf_ngtcp2_ctx *ctx = NULL;
|
||||
struct Curl_cfilter *cf = NULL;
|
||||
CURLcode result;
|
||||
|
||||
ctx = curlx_calloc(1, sizeof(*ctx));
|
||||
if(!ctx) {
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
cf_ngtcp2_ctx_init(ctx);
|
||||
|
||||
result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
|
||||
if(result)
|
||||
goto out;
|
||||
Curl_conn_cf_insert_after(cf_at, cf);
|
||||
cf->conn = cf_at->conn;
|
||||
out:
|
||||
if(result) {
|
||||
curlx_safefree(cf);
|
||||
cf_ngtcp2_ctx_free(ctx);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* Do not leak this filter's call_data accessor in unity builds. */
|
||||
#undef CF_CTX_CALL_DATA
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf,
|
|||
struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
struct Curl_sockaddr_ex *addr);
|
||||
|
||||
CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at);
|
||||
#endif
|
||||
|
||||
#endif /* HEADER_CURL_VQUIC_CURL_NGTCP2_H */
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ static void cf_quiche_ctx_close(struct cf_quiche_ctx *ctx)
|
|||
quiche_config_free(ctx->cfg);
|
||||
ctx->cfg = NULL;
|
||||
}
|
||||
Curl_ssl_peer_cleanup(&ctx->peer);
|
||||
}
|
||||
|
||||
static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
|
|||
return CURLE_FAILED_INIT;
|
||||
#endif
|
||||
(void)session_reuse_cb;
|
||||
if(peer->dest)
|
||||
Curl_ssl_peer_cleanup(peer);
|
||||
result = Curl_ssl_peer_init(peer, cf, tls_id, TRNSPRT_QUIC);
|
||||
if(result)
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -261,6 +261,44 @@ out:
|
|||
return result;
|
||||
}
|
||||
|
||||
/* Split QUIC payload by datagram (gso) boundaries when sending over a
|
||||
* non-UDP lower filter (for example CONNECT-UDP proxy tunnel). */
|
||||
static CURLcode send_packet_no_gso_cf(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
const uint8_t *pkt, size_t pktlen,
|
||||
size_t gsolen, size_t *psent)
|
||||
{
|
||||
const uint8_t *p, *end = pkt + pktlen;
|
||||
size_t sent, len;
|
||||
CURLcode result = CURLE_OK;
|
||||
VERBOSE(size_t calls = 0);
|
||||
|
||||
*psent = 0;
|
||||
|
||||
/* Send one datagram-sized chunk per call into the lower filter. */
|
||||
for(p = pkt; p < end; p += len) {
|
||||
len = CURLMIN(gsolen, (size_t)(end - p));
|
||||
result = Curl_conn_cf_send(cf->next, data, p, len, FALSE, &sent);
|
||||
/* Report forward progress even if we return CURLE_AGAIN later. */
|
||||
*psent += sent;
|
||||
VERBOSE(++calls);
|
||||
/* Preserve lower-filter errors (including CURLE_AGAIN). */
|
||||
if(result)
|
||||
goto out;
|
||||
if(sent < len) {
|
||||
/* We need whole datagrams here. Partial accept means blocked. */
|
||||
result = CURLE_AGAIN;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
CURL_TRC_CF(data, cf, "vquic_cf_send(len=%zu, gso=%zu, calls=%zu)"
|
||||
" -> %d, sent=%zu",
|
||||
pktlen, gsolen, calls, result, *psent);
|
||||
return result;
|
||||
}
|
||||
|
||||
static CURLcode vquic_send_packets(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct cf_quic_ctx *qctx,
|
||||
|
|
@ -310,7 +348,22 @@ CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|||
blen = qctx->split_len;
|
||||
}
|
||||
|
||||
result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent);
|
||||
if(qctx->sockfd != CURL_SOCKET_BAD) {
|
||||
/* Direct UDP socket (via happy eyeballs) */
|
||||
result = vquic_send_packets(cf, data, qctx, buf, blen, gsolen, &sent);
|
||||
}
|
||||
else {
|
||||
/* Tunneled QUIC (CONNECT-UDP through proxy) */
|
||||
if(gsolen && (blen > gsolen)) {
|
||||
/* Send one datagram at a time to preserve packet boundaries. */
|
||||
result = send_packet_no_gso_cf(cf, data, buf, blen, gsolen, &sent);
|
||||
}
|
||||
else {
|
||||
/* No GSO aggregate to split, regular lower-filter send is enough. */
|
||||
result = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &sent);
|
||||
}
|
||||
}
|
||||
|
||||
if(result) {
|
||||
if(result == CURLE_AGAIN) {
|
||||
Curl_bufq_skip(&qctx->sendbuf, sent);
|
||||
|
|
@ -699,6 +752,16 @@ CURLcode Curl_qlogdir(struct Curl_easy *data,
|
|||
return CURLE_OK;
|
||||
}
|
||||
|
||||
CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at)
|
||||
{
|
||||
#if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
|
||||
return Curl_cf_ngtcp2_insert_after(cf_at);
|
||||
#else
|
||||
(void)cf_at;
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
#endif
|
||||
}
|
||||
|
||||
CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
|
||||
struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
|
|
@ -737,10 +800,6 @@ CURLcode Curl_conn_may_http3(struct Curl_easy *data,
|
|||
failf(data, "HTTP/3 is not supported over a SOCKS proxy");
|
||||
return CURLE_URL_MALFORMAT;
|
||||
}
|
||||
if(conn->bits.httpproxy && conn->bits.tunnel_proxy) {
|
||||
failf(data, "HTTP/3 is not supported over an HTTP proxy");
|
||||
return CURLE_URL_MALFORMAT;
|
||||
}
|
||||
#endif
|
||||
|
||||
return CURLE_OK;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ CURLcode Curl_qlogdir(struct Curl_easy *data,
|
|||
size_t scidlen,
|
||||
int *qlogfdp);
|
||||
|
||||
CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at);
|
||||
|
||||
CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
|
||||
struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue