]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
cf-setup: improve readability
authorStefan Eissing <stefan@eissing.org>
Mon, 1 Jun 2026 12:23:30 +0000 (14:23 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 1 Jun 2026 14:47:24 +0000 (16:47 +0200)
Restructure the code in cf-setup connect to make it better readable what
is happening for establishing the connection's filter chain.

Closes #21827

lib/cf-h3-proxy.c
lib/connect.c
lib/http_proxy.c
tests/http/test_60_h3_proxy.py

index a78561538846e81d589c2925e20964496de5e159..4fdaef47d0cfe0a07b5d3da833b521e83b27cc23 100644 (file)
@@ -3449,17 +3449,6 @@ CURLcode Curl_cf_h3_proxy_create(struct Curl_cfilter **pcf,
   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) {
index 0ed7b22a5b48e41c1e699b6b449a6559ae672069..9d74e35bc76eef6486fba67fbdbb964392eadf73 100644 (file)
@@ -55,6 +55,7 @@
 #include "cfilters.h"
 #include "connect.h"
 #include "cf-dns.h"
+#include "cf-capsule.h"
 #include "cf-haproxy.h"
 #include "cf-https-connect.h"
 #include "cf-ip-happy.h"
@@ -342,174 +343,256 @@ struct cf_setup_ctx {
   uint8_t transport;
 };
 
-static CURLcode cf_setup_connect(struct Curl_cfilter *cf,
-                                 struct Curl_easy *data,
-                                 bool *done)
+#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;
-  struct Curl_peer *first_peer =
-    Curl_conn_get_first_peer(cf->conn, cf->sockindex);
-
-  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;
-  }
-
-  if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) {
-    /* 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");
+  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", result);
+        return result;
+      }
+      CURL_TRC_CF(data, cf, "added HAPROXY filter");
     }
-#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 = (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;
+    ctx->state = CF_SETUP_CNNCT_HAPROXY;
   }
+  return result;
+}
 
-  /* sub-chain connected, do we need to add more? */
-#ifndef CURL_DISABLE_PROXY
+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->bits.socksproxy) {
-    struct Curl_peer *dest; /* where SOCKS should tunnel to */
+    /* Add a SOCKS proxy to go through `first_peer` to `second_peer`*/
+    struct Curl_peer *second_peer;
 
     if(cf->conn->bits.httpproxy)
-      dest = cf->conn->http_proxy.peer;
+      second_peer = cf->conn->http_proxy.peer;
     else
-      dest = Curl_conn_get_destination(cf->conn, cf->sockindex);
-    if(!dest)
+      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, dest, cf->conn->ip_version,
+      cf, data, second_peer, cf->conn->ip_version,
       cf->conn->socks_proxy.proxytype,
       cf->conn->socks_proxy.creds);
-
     if(result) {
-      /* 'dest' might be freed now so it can't be dereferenced */
-      CURL_TRC_CF(data, cf, "added SOCKS filter failed -> %d", result);
+      CURL_TRC_CF(data, cf, "adding SOCKS filter failed -> %d", result);
       return result;
     }
-    CURL_TRC_CF(data, cf, "added SOCKS filter to %s:%u -> %d",
-                dest->hostname, dest->port, result);
+
+    CURL_TRC_CF(data, cf, "added SOCKS filter to %s:%u",
+                second_peer->hostname, second_peer->port);
     ctx->state = CF_SETUP_CNNCT_SOCKS;
-    if(!cf->next || !cf->next->connected)
-      goto connect_sub_chain;
   }
+  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->bits.httpproxy) {
 #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)
+      if(result) {
+        CURL_TRC_CF(data, cf, "adding SSL filter for HTTP proxy failed -> %d",
+                    result);
         return result;
+      }
+      CURL_TRC_CF(data, cf, "added SSL filter for HTTP proxy");
     }
 #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)
+      if(result) {
+        CURL_TRC_CF(data, cf, "adding HTTP proxy tunnel filter failed -> %d",
+                    result);
         return result;
+      }
+      CURL_TRC_CF(data, cf, "added HTTP proxy tunnel filter");
     }
-#endif /* !CURL_DISABLE_HTTP */
     ctx->state = CF_SETUP_CNNCT_HTTP_PROXY;
-    if(!cf->next || !cf->next->connected)
-      goto connect_sub_chain;
   }
-#endif /* !CURL_DISABLE_PROXY */
+  return result;
+}
+#endif /* !CURL_DISABLE_HTTP */
+#endif /* CURL_DISABLE_PROXY */
 
-  if(ctx->state < CF_SETUP_CNNCT_HAPROXY) {
-#ifndef CURL_DISABLE_PROXY
-    if(data->set.haproxyprotocol) {
-      if(ctx->transport == TRNSPRT_QUIC) {
-        failf(data, "haproxy protocol not support QUIC");
+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 fist hop we directly connect to and what transport
+     * do we use for it? Only on the first hop we can do Happy Eyeballs. */
+    struct Curl_peer *first_peer =
+      Curl_conn_get_first_peer(cf->conn, cf->sockindex);
+    uint8_t first_transport = ctx->transport;
+    bool tunnel_proxy = FALSE;
+
+#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
+    if(cf->conn->bits.httpproxy && cf->conn->bits.tunnel_proxy) {
+      first_transport =
+        Curl_http_proxy_transport(cf->conn->http_proxy.proxytype);
+      if((first_transport == TRNSPRT_QUIC) && (cf->conn->bits.socksproxy)) {
+        failf(data, "HTTP/3 proxy not possible via SOCKS");
         return CURLE_UNSUPPORTED_PROTOCOL;
       }
-      result = Curl_cf_haproxy_insert_after(cf, data);
-      if(result)
-        return result;
+      tunnel_proxy = TRUE;
+    }
+#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
+
+    result = cf_ip_happy_insert_after(cf, data, first_peer,
+                                      ctx->transport, first_transport,
+                                      tunnel_proxy);
+    if(result) {
+      CURL_TRC_CF(data, cf, "adding happy eyeballs failed -> %d", result);
+      return result;
+    }
+
+    if(tunnel_proxy && (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_proxy ? "proxy" : "origin",
+                  first_peer->hostname, first_peer->port);
+      ctx->state = CF_SETUP_CNNCT_EYEBALLS;
     }
-#endif /* !CURL_DISABLE_PROXY */
-    ctx->state = CF_SETUP_CNNCT_HAPROXY;
-    if(!cf->next || !cf->next->connected)
-      goto connect_sub_chain;
   }
+  return result;
+}
 
-  /* Adding Curl_cf_quic_insert_after() because now we
-     need the next filter to be QUIC/HTTP/3 (which has SSL) */
+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(USE_PROXY_HTTP3)
-  if(ctx->transport == TRNSPRT_QUIC && cf->conn->bits.httpproxy &&
-     cf->conn->bits.tunnel_proxy &&
-     (data->state.http_neg.wanted == CURL_HTTP_V3x)) {
-    if(ctx->state < CF_SETUP_CNNCT_SSL) {
+    !defined(CURL_DISABLE_PROXY)
+    /* Wanting QUIC with a 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->bits.httpproxy &&
+       cf->conn->bits.tunnel_proxy) {
+      result = Curl_cf_capsule_insert_after(cf, data);
+      if(result) {
+        CURL_TRC_CF(data, cf, "adding capsule filter failed -> %d", result);
+        return result;
+      }
       result = Curl_cf_quic_insert_after(cf);
-      if(result)
+      if(result) {
+        CURL_TRC_CF(data, cf, "adding QUIC filter failed -> %d", result);
         return result;
-      ctx->state = CF_SETUP_CNNCT_SSL;
+      }
+      CURL_TRC_CF(data, cf, "added QUIC filter for origin");
     }
-    if(!cf->next || !cf->next->connected)
-      goto connect_sub_chain;
-  }
-  else
-#endif /* !CURL_DISABLE_HTTP && USE_HTTP3 && USE_PROXY_HTTP3 */
-  {
-    if(ctx->state < CF_SETUP_CNNCT_SSL) {
+    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 */
-        result = Curl_cf_ssl_insert_after(cf, data);
-        if(result)
-          return result;
+    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 */
+      result = Curl_cf_ssl_insert_after(cf, data);
+      if(result) {
+        CURL_TRC_CF(data, cf, "adding SSL filter for origin failed -> %d",
+                    result);
+        return result;
       }
-#endif /* USE_SSL */
-      ctx->state = CF_SETUP_CNNCT_SSL;
-      if(!cf->next || !cf->next->connected)
-        goto connect_sub_chain;
+      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(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;
index 39cfb12446818d324fd483625f6b3786d5085d7a..9f1ed7963c5ce5628313225b5792e1badc764b31 100644 (file)
@@ -654,22 +654,8 @@ connect_sub:
   }
   else {
     /* subchain connected and we had already installed the protocol filter.
-     * This means the protocol tunnel is established, we are done.
-     */
+     * This means the protocol tunnel is established, we are done. */
     DEBUGASSERT(ctx->sub_filter_installed);
-    if(ctx->udp_tunnel) {
-#ifdef USE_PROXY_HTTP3
-      /* Insert capsule filter between us and the protocol sub-filter.
-       * This handles encap/decap of UDP datagrams in capsule format. */
-      result = Curl_cf_capsule_insert_after(cf, data);
-      if(result)
-        goto out;
-      CURL_TRC_CF(data, cf, "installed capsule filter for UDP tunnel");
-#else
-      result = CURLE_NOT_BUILT_IN;
-      goto out;
-#endif /* USE_PROXY_HTTP3 */
-    }
     result = CURLE_OK;
   }
 
index 7ebba8a2b19dde91e2d411e27b02677054589587..39c6d265f6f159710015fd28103bedba62f5cff2 100644 (file)
@@ -195,12 +195,12 @@ class TestH3Proxy:
     @pytest.mark.parametrize(
         ["alpn_proto", "proxy_proto", "exp_err"],
         [
-            #pytest.param(
-            #    "http/1.1",
-            #    "h3",
-            #    "could not connect to server",
-            #    id="fail_h1_over_h3_proxytunnel",
-            #),
+            pytest.param(
+                "http/1.1",
+                "h3",
+                "could not connect to server",
+                id="fail_h1_over_h3_proxytunnel",
+            ),
             pytest.param(
                 "h2",
                 "h3",
@@ -208,12 +208,12 @@ class TestH3Proxy:
                 marks=MARK_NEEDS_NGHTTP2,
                 id="fail_h2_over_h3_proxytunnel",
             ),
-            #pytest.param(
-            #    "h3",
-            #    "h3",
-            #    "could not connect to server",
-            #    id="fail_h3_over_h3_proxytunnel",
-            #),
+            pytest.param(
+                "h3",
+                "h3",
+                "could not connect to server",
+                id="fail_h3_over_h3_proxytunnel",
+            ),
             #pytest.param(
             #    "h3",
             #    "h2",
@@ -235,11 +235,13 @@ class TestH3Proxy:
         httpd,
         nghttpx,
         nghttpx_fwd,
+        h2o_proxy,
         alpn_proto,
         proxy_proto,
         exp_err,
     ):
-        _require_available(httpd=httpd, nghttpx=nghttpx, nghttpx_fwd=nghttpx_fwd)
+        _require_available(httpd=httpd, nghttpx=nghttpx, nghttpx_fwd=nghttpx_fwd,
+                           h2o_proxy=h2o_proxy)
 
         curl = CurlClient(env=env)
         url = f"https://localhost:{env.https_port}/data.json"