mirror of
https://github.com/curl/curl
synced 2026-08-25 12:32:35 -04:00
Use separate dns cache entries for addresses (A+AAAA) and HTTPS-RR results. That makes also "negative" results independent of each other. Dns cache entries, once in use, can no longer be modified safely, as concurrent use would require each access then to be done under lock. By using separate entries, we can update a HTTPS entry without needing to duplicate an existing address entry for the same host+port. Connection filters can now ask for DNS resolves. This works at any time during connection setup and while connect is going on. `Curl_conn_dns_add_addr_resolve()` and `Curl_conn_dns_add_https_resolve()` are used for that. They check if the resolve is already ongoing, can be added to a resolve not started yet or, as last resort, create a new filter instance and add it to the connection (it's easier to add more filters than making the same filter handle multiple resolves. Since DNS filters are removed once the connection is established, there is no later penalty). HTTPS-RR queries are added by the `HTTPS-CONNECT`, `SSL` and `QUIC` filters. The latter will only do that when ECH is configured and supported. That means we trigger HTTPS-RR queries only when the results matter. Add test_22_06 for ALPN influenced via HTTPS-RR when tunneling through a proxy. This did not work before. Adjust test2100 to use https: as the previous http: URL no longer triggers HTTPS-RR resolves. Closes #22216
479 lines
15 KiB
C
479 lines
15 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
*
|
|
* This software is licensed as described in the file COPYING, which
|
|
* you should have received as part of this distribution. The terms
|
|
* are also available at https://curl.se/docs/copyright.html.
|
|
*
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
* SPDX-License-Identifier: curl
|
|
*
|
|
***************************************************************************/
|
|
#include "curl_setup.h"
|
|
|
|
#include "urldata.h"
|
|
#include "cfilters.h"
|
|
#include "cf-haproxy.h"
|
|
#include "cf-ip-happy.h"
|
|
#include "cf-setup.h"
|
|
#include "curl_trc.h"
|
|
#include "connect.h"
|
|
#include "http_proxy.h"
|
|
#include "socks.h"
|
|
#include "vquic/cf-capsule.h"
|
|
#include "vquic/vquic.h"
|
|
#include "vtls/vtls.h"
|
|
|
|
|
|
typedef enum {
|
|
CF_SETUP_INIT,
|
|
CF_SETUP_CNNCT_EYEBALLS,
|
|
CF_SETUP_CNNCT_SOCKS,
|
|
CF_SETUP_CNNCT_HTTP_PROXY,
|
|
CF_SETUP_CNNCT_HAPROXY,
|
|
CF_SETUP_CNNCT_SSL,
|
|
CF_SETUP_DONE
|
|
} cf_setup_state;
|
|
|
|
struct cf_setup_ctx {
|
|
cf_setup_state state;
|
|
int ssl_mode;
|
|
uint8_t transport;
|
|
uint8_t retry_count;
|
|
};
|
|
|
|
#ifndef CURL_DISABLE_PROXY
|
|
|
|
static CURLcode cf_setup_add_haproxy(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_setup_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
if(ctx->state < CF_SETUP_CNNCT_HAPROXY) {
|
|
if(data->set.haproxyprotocol) {
|
|
if(ctx->transport == TRNSPRT_QUIC) {
|
|
failf(data, "haproxy protocol does not support QUIC");
|
|
return CURLE_UNSUPPORTED_PROTOCOL;
|
|
}
|
|
result = Curl_cf_haproxy_insert_after(cf, data);
|
|
if(result) {
|
|
CURL_TRC_CF(data, cf, "adding HAPROXY filter failed -> %d",
|
|
(int)result);
|
|
return result;
|
|
}
|
|
CURL_TRC_CF(data, cf, "added HAPROXY filter");
|
|
}
|
|
ctx->state = CF_SETUP_CNNCT_HAPROXY;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_setup_add_socks(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_setup_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->socks_proxy.peer) {
|
|
/* Add a SOCKS proxy to go through `first_peer` to `second_peer`*/
|
|
struct Curl_peer *second_peer;
|
|
|
|
if(cf->conn->http_proxy.peer)
|
|
second_peer = cf->conn->http_proxy.peer;
|
|
else
|
|
second_peer = Curl_conn_get_destination(cf->conn, cf->sockindex);
|
|
if(!second_peer)
|
|
return CURLE_FAILED_INIT;
|
|
|
|
result = Curl_cf_socks_proxy_insert_after(
|
|
cf, data, second_peer, cf->conn->ip_version,
|
|
cf->conn->socks_proxy.proxytype,
|
|
cf->conn->socks_proxy.creds);
|
|
if(result) {
|
|
CURL_TRC_CF(data, cf, "adding SOCKS filter failed -> %d", (int)result);
|
|
return result;
|
|
}
|
|
|
|
CURL_TRC_CF(data, cf, "added SOCKS filter to %s:%u",
|
|
second_peer->hostname, second_peer->port);
|
|
ctx->state = CF_SETUP_CNNCT_SOCKS;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#ifndef CURL_DISABLE_HTTP
|
|
static CURLcode cf_setup_add_http_proxy(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_setup_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY &&
|
|
cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) {
|
|
struct Curl_peer *peer = cf->conn->http_proxy.peer;
|
|
struct Curl_peer *tunnel_peer =
|
|
Curl_conn_get_destination(cf->conn, cf->sockindex);
|
|
|
|
#ifdef USE_SSL
|
|
if(CURL_PROXY_IS_HTTPS(cf->conn->http_proxy.proxytype) &&
|
|
!Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
|
|
result = Curl_cf_ssl_proxy_insert_after(
|
|
cf, data, cf->conn->http_proxy.peer);
|
|
if(result) {
|
|
CURL_TRC_CF(data, cf, "adding SSL filter for HTTP proxy failed -> %d",
|
|
(int)result);
|
|
return result;
|
|
}
|
|
CURL_TRC_CF(data, cf, "added SSL filter for HTTP proxy");
|
|
}
|
|
#endif /* USE_SSL */
|
|
|
|
result = Curl_cf_http_proxy_insert_after(
|
|
cf, data, peer, tunnel_peer,
|
|
ctx->transport, cf->conn->http_proxy.proxytype);
|
|
if(result) {
|
|
CURL_TRC_CF(data, cf, "adding HTTP proxy tunnel filter failed -> %d",
|
|
(int)result);
|
|
return result;
|
|
}
|
|
CURL_TRC_CF(data, cf, "added HTTP proxy tunnel filter");
|
|
ctx->state = CF_SETUP_CNNCT_HTTP_PROXY;
|
|
}
|
|
return result;
|
|
}
|
|
#endif /* !CURL_DISABLE_HTTP */
|
|
#endif /* CURL_DISABLE_PROXY */
|
|
|
|
/* Get the origin curl connects its socket to.
|
|
* Can be origin or the first proxy. */
|
|
static struct Curl_peer *conn_get_first_origin(struct connectdata *conn,
|
|
int sockindex)
|
|
{
|
|
#ifndef CURL_DISABLE_PROXY
|
|
if(conn->socks_proxy.peer)
|
|
return conn->socks_proxy.peer;
|
|
if(conn->http_proxy.peer)
|
|
return conn->http_proxy.peer;
|
|
#endif
|
|
return (sockindex == SECONDARYSOCKET) ? conn->origin2 : conn->origin;
|
|
}
|
|
|
|
static CURLcode cf_setup_add_ip_happy(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_setup_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) {
|
|
/* What is the first hop we directly connect to and what transport
|
|
* do we use for it? Only on the first hop we can do Happy Eyeballs.
|
|
* first_origin and first_peer differ on --connect-to. */
|
|
struct Curl_peer *first_origin =
|
|
conn_get_first_origin(cf->conn, cf->sockindex);
|
|
struct Curl_peer *first_peer =
|
|
Curl_conn_get_first_peer(cf->conn, cf->sockindex);
|
|
struct Curl_peer *tunnel_peer = NULL;
|
|
uint8_t first_transport = ctx->transport;
|
|
|
|
if(!first_peer)
|
|
return CURLE_FAILED_INIT;
|
|
|
|
#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
|
|
if(cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) {
|
|
first_transport =
|
|
Curl_http_proxy_transport(cf->conn->http_proxy.proxytype);
|
|
tunnel_peer = Curl_conn_get_destination(cf->conn, cf->sockindex);
|
|
if((first_transport == TRNSPRT_QUIC) && cf->conn->socks_proxy.peer) {
|
|
failf(data, "HTTP/3 proxy not possible via SOCKS");
|
|
return CURLE_UNSUPPORTED_PROTOCOL;
|
|
}
|
|
}
|
|
#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
|
|
|
|
result = Curl_cf_ip_happy_insert_after(cf, data, first_origin, first_peer,
|
|
first_transport,
|
|
tunnel_peer, ctx->transport);
|
|
if(result) {
|
|
CURL_TRC_CF(data, cf, "adding happy eyeballs failed -> %d", (int)result);
|
|
return result;
|
|
}
|
|
|
|
if(tunnel_peer && (first_transport == TRNSPRT_QUIC)) {
|
|
CURL_TRC_CF(data, cf, "happy eyeballing to HTTP/3 proxy %s:%u",
|
|
first_peer->hostname, first_peer->port);
|
|
ctx->state = CF_SETUP_CNNCT_HTTP_PROXY;
|
|
}
|
|
else {
|
|
CURL_TRC_CF(data, cf, "happy eyeballing to %s %s:%u",
|
|
tunnel_peer ? "proxy" : "origin",
|
|
first_peer->hostname, first_peer->port);
|
|
ctx->state = CF_SETUP_CNNCT_EYEBALLS;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_setup_add_origin_filters(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_setup_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
(void)data; /* not used in all builds */
|
|
if(ctx->state < CF_SETUP_CNNCT_SSL) {
|
|
#if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3) && \
|
|
!defined(CURL_DISABLE_PROXY)
|
|
|
|
/* Wanting QUIC with an HTTP tunneling filter, we now need to add
|
|
* the QUIC filter on top. Without tunneling, this has already
|
|
* happened in the Happy Eyeball filter. */
|
|
if(ctx->transport == TRNSPRT_QUIC &&
|
|
cf->conn->http_proxy.peer && !cf->conn->bits.origin_is_proxy) {
|
|
struct Curl_peer *origin = Curl_conn_get_origin(cf->conn, cf->sockindex);
|
|
struct Curl_peer *peer =
|
|
Curl_conn_get_destination(cf->conn, cf->sockindex);
|
|
|
|
result = Curl_cf_capsule_insert_after(cf, data);
|
|
if(result) {
|
|
CURL_TRC_CF(data, cf, "adding capsule filter failed -> %d",
|
|
(int)result);
|
|
return result;
|
|
}
|
|
result = Curl_cf_quic_insert_after(cf, data, origin, peer);
|
|
if(result) {
|
|
CURL_TRC_CF(data, cf, "adding QUIC filter failed -> %d", (int)result);
|
|
return result;
|
|
}
|
|
CURL_TRC_CF(data, cf, "added QUIC filter for origin");
|
|
}
|
|
else
|
|
#endif /* !CURL_DISABLE_HTTP && USE_HTTP3 && CURL_DISABLE_PROXY */
|
|
#ifdef USE_SSL
|
|
if((ctx->ssl_mode == CURL_CF_SSL_ENABLE ||
|
|
(ctx->ssl_mode != CURL_CF_SSL_DISABLE &&
|
|
cf->conn->scheme->flags & PROTOPT_SSL)) && /* we want SSL */
|
|
!Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */
|
|
|
|
#ifndef CURL_DISABLE_PROXY
|
|
if(cf->conn->bits.origin_is_proxy) {
|
|
result = Curl_cf_ssl_proxy_insert_after(cf, data, cf->conn->origin);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* FTP is a bitch. Wherever we really connect to on the DATA
|
|
* (secondary) connection, many servers require TLS sessions reuse
|
|
* to prove they are talking to the same client.
|
|
* For the TLS session lookup to work, we need to instantiate
|
|
* the SSL filter with the same peers as FIRSTSOCKET. See #22225
|
|
* Meaning: cf->sockindex does not matter here. */
|
|
result = Curl_cf_ssl_insert_after(cf, data,
|
|
Curl_conn_get_origin(cf->conn, FIRSTSOCKET),
|
|
Curl_conn_get_destination(cf->conn, FIRSTSOCKET));
|
|
}
|
|
if(result) {
|
|
CURL_TRC_CF(data, cf, "adding SSL filter for origin failed -> %d",
|
|
(int)result);
|
|
return result;
|
|
}
|
|
CURL_TRC_CF(data, cf, "added SSL filter for origin");
|
|
}
|
|
#endif /* USE_SSL */
|
|
ctx->state = CF_SETUP_CNNCT_SSL;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_setup_connect_steps(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
bool *done)
|
|
{
|
|
struct cf_setup_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
if(cf->connected) {
|
|
*done = TRUE;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
/* connect current sub-chain */
|
|
connect_sub_chain:
|
|
VERBOSE(Curl_conn_trc_filters(data, cf->sockindex, "cf_setup_connect"));
|
|
|
|
if(cf->next && !cf->next->connected) {
|
|
result = Curl_conn_cf_connect(cf->next, data, done);
|
|
if(result || !*done)
|
|
return result;
|
|
}
|
|
|
|
result = cf_setup_add_ip_happy(cf, data);
|
|
if(result)
|
|
return result;
|
|
if(!cf->next || !cf->next->connected)
|
|
goto connect_sub_chain;
|
|
|
|
#ifndef CURL_DISABLE_PROXY
|
|
result = cf_setup_add_socks(cf, data);
|
|
if(result)
|
|
return result;
|
|
if(!cf->next || !cf->next->connected)
|
|
goto connect_sub_chain;
|
|
|
|
#ifndef CURL_DISABLE_HTTP
|
|
result = cf_setup_add_http_proxy(cf, data);
|
|
if(result)
|
|
return result;
|
|
if(!cf->next || !cf->next->connected)
|
|
goto connect_sub_chain;
|
|
#endif /* !CURL_DISABLE_HTTP */
|
|
|
|
result = cf_setup_add_haproxy(cf, data);
|
|
if(result)
|
|
return result;
|
|
if(!cf->next || !cf->next->connected)
|
|
goto connect_sub_chain;
|
|
#endif /* !CURL_DISABLE_PROXY */
|
|
|
|
result = cf_setup_add_origin_filters(cf, data);
|
|
if(result)
|
|
return result;
|
|
if(!cf->next || !cf->next->connected)
|
|
goto connect_sub_chain;
|
|
|
|
ctx->state = CF_SETUP_DONE;
|
|
cf->connected = TRUE;
|
|
*done = TRUE;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static CURLcode cf_setup_connect(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
bool *done)
|
|
{
|
|
struct cf_setup_ctx *ctx = cf->ctx;
|
|
CURLcode result;
|
|
|
|
/* In some situations, a server/proxy may close the connection and
|
|
* we need to connect again (HTTP/1.x proxy auth, for example).
|
|
* We used to close the filters and reuse them for another attempt,
|
|
* however that complicates filter code and it is simpler to tear them
|
|
* all down and start over. */
|
|
retry:
|
|
result = cf_setup_connect_steps(cf, data, done);
|
|
|
|
if(result == CURLE_AGAIN) {
|
|
++ctx->retry_count;
|
|
if(ctx->retry_count > 5) /* arbitrary limit, better just timeout? */
|
|
return CURLE_COULDNT_CONNECT;
|
|
|
|
CURL_TRC_CF(data, cf, "retrying connect, %d. time", ctx->retry_count);
|
|
Curl_conn_cf_discard_chain(&cf->next, data);
|
|
ctx->state = CF_SETUP_INIT;
|
|
goto retry;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
|
|
{
|
|
struct cf_setup_ctx *ctx = cf->ctx;
|
|
|
|
CURL_TRC_CF(data, cf, "destroy");
|
|
curlx_safefree(ctx);
|
|
}
|
|
|
|
struct Curl_cftype Curl_cft_setup = {
|
|
"SETUP",
|
|
CF_TYPE_SETUP,
|
|
CURL_LOG_LVL_NONE,
|
|
cf_setup_destroy,
|
|
cf_setup_connect,
|
|
Curl_cf_def_shutdown,
|
|
Curl_cf_def_adjust_pollset,
|
|
Curl_cf_def_data_pending,
|
|
Curl_cf_def_send,
|
|
Curl_cf_def_recv,
|
|
Curl_cf_def_cntrl,
|
|
Curl_cf_def_conn_is_alive,
|
|
Curl_cf_def_conn_keep_alive,
|
|
Curl_cf_def_query,
|
|
};
|
|
|
|
static CURLcode cf_setup_create(struct Curl_cfilter **pcf,
|
|
struct Curl_easy *data,
|
|
uint8_t transport,
|
|
int ssl_mode)
|
|
{
|
|
struct Curl_cfilter *cf = NULL;
|
|
struct cf_setup_ctx *ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
(void)data;
|
|
ctx = curlx_calloc(1, sizeof(*ctx));
|
|
if(!ctx) {
|
|
result = CURLE_OUT_OF_MEMORY;
|
|
goto out;
|
|
}
|
|
ctx->state = CF_SETUP_INIT;
|
|
ctx->ssl_mode = ssl_mode;
|
|
ctx->transport = transport;
|
|
|
|
result = Curl_cf_create(&cf, &Curl_cft_setup, ctx);
|
|
if(result)
|
|
goto out;
|
|
ctx = NULL;
|
|
|
|
out:
|
|
*pcf = result ? NULL : cf;
|
|
if(ctx) {
|
|
curlx_free(ctx);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
CURLcode Curl_cf_setup_add(struct Curl_easy *data,
|
|
struct connectdata *conn,
|
|
int sockindex,
|
|
uint8_t transport,
|
|
int ssl_mode)
|
|
{
|
|
struct Curl_cfilter *cf;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
DEBUGASSERT(data);
|
|
result = cf_setup_create(&cf, data, transport, ssl_mode);
|
|
if(result)
|
|
goto out;
|
|
Curl_conn_cf_add(data, conn, sockindex, cf);
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at,
|
|
struct Curl_easy *data,
|
|
uint8_t transport,
|
|
int ssl_mode)
|
|
{
|
|
struct Curl_cfilter *cf;
|
|
CURLcode result;
|
|
|
|
DEBUGASSERT(data);
|
|
result = cf_setup_create(&cf, data, transport, ssl_mode);
|
|
if(result)
|
|
goto out;
|
|
Curl_conn_cf_insert_after(cf_at, cf);
|
|
out:
|
|
return result;
|
|
}
|