curl/curl
Connection filters
Active contributors: Stefan Eissing, Daniel Stenberg
Purpose
Connection filters ("cfilters") are the layered I/O abstraction beneath every libcurl connection. A filter has connect, cntrl, send, recv, query, and shutdown callbacks, plus a single optional sub-filter underneath. Stacking filters lets curl support HTTPS-over-HTTP-proxy, HTTP/2 multiplexing on top of TLS on top of TCP, HTTP/3 on top of QUIC on top of UDP, and the PROXY protocol — without any of the protocol handlers needing to know how it works.
The framework lives in lib/cfilters.c (34 KB) and 29 KB). Each concrete filter is a lib/cfilters.h (cf-*.c file in lib/ or under lib/vtls/ and lib/vquic/.
Key abstractions
| Symbol | Definition | Description |
|---|---|---|
struct Curl_cfilter |
lib/cfilters.h |
One filter instance: parent connection, sub-filter, ops vtable, opaque context |
struct Curl_cftype |
lib/cfilters.h |
The vtable shared by all instances of one filter type |
Curl_cf_create() |
lib/cfilters.c |
Helper to allocate a filter and initialize it |
Curl_conn_cf_add() |
lib/cfilters.c |
Push a new filter on top of the chain |
Curl_conn_cf_insert_after() |
lib/cfilters.c |
Insert a filter between two existing ones |
Curl_conn_send/recv |
lib/cfilters.c |
Top-of-stack send/recv used by protocol handlers |
Curl_conn_connect() |
lib/cfilters.c |
Drives connect down the chain |
Curl_conn_data_pending() |
lib/cfilters.c |
Asks the filter chain whether it has buffered bytes ready |
The filter types
Each filter is identified by its Curl_cftype. The major filters in the source tree:
| Filter | Source | Purpose |
|---|---|---|
cf-socket |
lib/cf-socket.c |
Bottom-of-stack: a real TCP/UDP/Unix socket |
cf-ip-happy |
lib/cf-ip-happy.c |
Parallel IPv4/IPv6 connect race |
cf-dns |
lib/cf-dns.c |
Re-resolves at reconnect time |
cf-https-connect |
lib/cf-https-connect.c |
HTTPS-over-CONNECT helper |
cf-h1-proxy |
lib/cf-h1-proxy.c |
HTTP/1 CONNECT tunnel through a proxy |
cf-h2-proxy |
lib/cf-h2-proxy.c |
HTTP/2 CONNECT tunnel (multiplexed streams) |
cf-haproxy |
lib/cf-haproxy.c |
PROXY-protocol prelude |
socks* |
lib/socks.c, lib/socks_*.c |
SOCKS4/4a/5 + SOCKS5 with GSSAPI/SSPI |
| TLS filters | lib/vtls/vtls.c + each backend |
One filter per supported TLS backend |
| HTTP/2 filter | lib/http2.c |
Multiplexes streams over a single connection |
| HTTP/3 filters | lib/vquic/curl_ngtcp2.c, lib/vquic/curl_quiche.c |
QUIC + HTTP/3 backends |
How it works
Every connectdata owns a cfilter chain (conn->cfilter[], indexed by socket index for multi-socket protocols like FTP). A protocol handler calls Curl_conn_send/Curl_conn_recv and the call descends the chain:
graph TD
A[http.c calls Curl_conn_send] --> B[cf_http2: stream framing]
B --> C[cf_tls=OpenSSL: encrypt]
C --> D[cf_h1_proxy: forward as is]
D --> E[cf_socket: write to FD]recv flows the other way. connect is similar — the top filter calls connect on its sub-filter recursively until the bottom socket dials and the chain settles.
A typical HTTPS-through-HTTPS-proxy connection has this stack from bottom up:
cf_socket ← real TCP socket
cf_tls (proxy TLS backend) ← TLS to the proxy
cf_h1_proxy or cf_h2_proxy ← CONNECT request through the encrypted proxy
cf_tls (origin TLS backend) ← TLS to origin, layered through the tunnel
cf_http2 (or none for HTTP/1.1) ← HTTP/2 multiplexerFor HTTP/3 the bottom is cf_socket over UDP, then cf_quic_*, then HTTP/3 framing.
Lifecycle
- Build: when a connection is created in
lib/url.c,Curl_setup_cfilter_chain(and protocol-specific helpers likeCurl_http_proxy_setup,Curl_ssl_cfilter_add,Curl_http2_switch) push filters onto the chain. - Connect:
Curl_conn_connectwalks the chain calling eachconnect. Filters returnCURLE_AGAINto yield until ready. - Operate: protocol handlers send/recv at the top of the stack.
- Adjust:
Curl_conn_adjust_pollsetlets each filter declare which FDs it wants polled (e.g. TLS in handshake state vs steady state). - Shutdown:
Curl_conn_shutdownwalks the chain top-down for graceful close, then bottom-up for socket close. - Free:
Curl_conn_closereleases all filters and their contexts.
Why this design
Before cfilters existed (they were introduced in 2022 and grew through 2023), each new transport meant another if (data->conn->ssl_config…) branch in lib/transfer.c, lib/sendf.c, lib/connect.c, and the proxy code. The cfilter framework collapses that into "implement a struct Curl_cftype" and the rest works.
It also lets transformations stack cleanly:
- HAProxy protocol:
cf-haproxywrites a single PROXY header, then becomes transparent. - HTTPS proxy: just another TLS filter under the proxy CONNECT filter.
- HTTP/3 with optional 0-RTT: implemented inside
vquicfilters, invisible tohttp.c.
Integration points
- Transfer engine: drives
Curl_conn_connect,Curl_conn_send,Curl_conn_recv,Curl_conn_adjust_pollset. See Transfer engine. - Protocol handlers: only see the top of the chain. They never know whether they are talking through TLS or a tunnel.
- TLS, HTTP/2, HTTP/3: each is a filter type; their internals are detailed under Features.
- Diagnostics:
Curl_conn_metalets each filter expose properties (e.g. cipher suite, ALPN result) for--write-outandgetinfo.
Entry points for modification
- Adding a new transport → write a
Curl_cftypein a newlib/cf-*.c, expose a constructor (Curl_cf_<name>_create), and have the connection-setup code inlib/url.cpush it onto the chain when appropriate. - Changing how an existing filter handshakes → implement the change inside the filter's
do_connect; the rest of the codebase does not need to know. - Diagnostic queries → add a
queryoperation toCurl_cftypeand route it viaCurl_conn_get_property.
Related pages
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.