Open-Source Wikis

/

curl

/

curl

/

Architecture

curl/curl

Architecture

This page is a map of the curl source tree and the runtime stack that delivers a transfer. Read it before diving into any specific subsystem — almost every other wiki page assumes you already know what an "easy handle", a "connection filter", and the "multi loop" are.

Source tree at a glance

curl/
├── include/curl/   # Public C API headers (curl.h, multi.h, urlapi.h, …)
├── lib/            # libcurl implementation (~184 .c, ~178 .h)
│   ├── vtls/       # TLS backends (OpenSSL, GnuTLS, mbedTLS, Schannel, wolfSSL, Rustls, Apple)
│   ├── vquic/      # QUIC/HTTP-3 backends (ngtcp2, quiche)
│   ├── vauth/      # SASL, NTLM, Digest, Kerberos, SPNEGO authentication
│   ├── vssh/       # SSH backends (libssh, libssh2)
│   └── curlx/      # Internal portability helpers shared between lib/ and src/
├── src/            # The `curl` command-line tool
│   └── toolx/      # Helpers shared between src/ tools
├── docs/           # Markdown sources rendered to man pages and the website
├── tests/          # Test harness and ~1995 functional + ~300 unit tests
├── scripts/        # Maintenance scripts (managen, checksrc, mk-ca-bundle, …)
└── m4/, CMake/     # Autotools and CMake build helpers

Three layered models

curl's architecture is best understood as three intersecting layers: the API surface the application sees, the transfer engine that schedules work, and the filter stack that turns bytes into protocol exchanges.

graph TD
    subgraph "Application"
      App[curl tool or third-party app]
    end
    subgraph "API surface (include/curl/)"
      Easy[easy interface\ncurl_easy_*]
      Multi[multi interface\ncurl_multi_*]
      URL[URL API\ncurl_url_*]
      Hdr[header API\ncurl_easy_header]
      WS[websockets\ncurl_ws_*]
    end
    subgraph "Transfer engine (lib/)"
      Multih[multi.c<br/>Curl_multi state machine]
      Trans[transfer.c<br/>SINGLEREQUEST]
      Conn[conncache.c<br/>connection cache]
      DNS[dnscache.c + asyn-*<br/>name resolver]
    end
    subgraph "Connection filter stack (lib/)"
      Cfilters[cfilters.c<br/>Curl_cfilter chain]
      Socket[cf-socket]
      TLS[vtls/* TLS filter]
      H2[http2.c HTTP/2 filter]
      H3[vquic/* HTTP/3 filter]
      Proxy[cf-h1-proxy / cf-h2-proxy / socks]
    end
    subgraph "Protocol handlers (lib/)"
      HTTP[http.c]
      FTP[ftp.c]
      SMTP[smtp.c, imap.c, pop3.c]
      Misc[telnet.c, tftp.c, dict.c, smb.c, mqtt.c, ldap.c, ws.c, rtsp.c, gopher.c, file.c]
    end

    App --> Easy
    App --> Multi
    App --> URL
    App --> Hdr
    App --> WS
    Easy --> Multih
    Multi --> Multih
    Multih --> Trans
    Multih --> Conn
    Multih --> DNS
    Trans --> HTTP
    Trans --> FTP
    Trans --> SMTP
    Trans --> Misc
    HTTP --> Cfilters
    FTP --> Cfilters
    SMTP --> Cfilters
    Misc --> Cfilters
    Cfilters --> Socket
    Cfilters --> TLS
    Cfilters --> H2
    Cfilters --> H3
    Cfilters --> Proxy

1. The API surface

The public API is in include/curl/. Applications interact with libcurl via:

  • The easy interface (curl_easy_init, curl_easy_setopt, curl_easy_perform, curl_easy_getinfo) for synchronous one-off transfers (include/curl/easy.h)
  • The multi interface (curl_multi_init, curl_multi_add_handle, curl_multi_perform, curl_multi_socket_action) for non-blocking, concurrent transfers (include/curl/multi.h)
  • The URL API for parsing and composing URLs without performing a transfer (include/curl/urlapi.h)
  • The header API for inspecting HTTP response headers (include/curl/header.h)
  • The websockets API for ws:///wss:// framed messaging (include/curl/websockets.h)

The "easy" interface is implemented as a wrapper around the multi interface, sharing all the heavy lifting. See API reference for details.

2. The transfer engine

The heart of libcurl is Curl_multi_runsingle() in lib/multi.c — a state machine that walks an "easy handle" through every step of a transfer:

INIT → PENDING → SETUP → CONNECT → RESOLVING → CONNECTING → PROTOCONNECT
     → PROTOCONNECTING → DO → DOING → DID → PERFORMING → RATELIMITING
     → DONE → COMPLETED

Each state has a handler. The state machine is non-blocking: it advances handles only when sockets are ready, and yields back to the application between events. The "multi" loop is the only loop; "easy_perform" is just multi_add + multi_perform + multi_remove + multi_cleanup wrapped in a blocking call.

Two long-lived caches sit alongside the state machine:

  • The connection cache (lib/conncache.c) keeps idle connections alive for reuse, indexed by destination hash. A single shared cache can be attached via curl_share.
  • The DNS cache (lib/dnscache.c) keeps resolved addresses for the configured TTL. The actual resolution work runs through lib/asyn-base.c plus one of asyn-ares.c (c-ares) or asyn-thrdd.c (thread pool), or lib/doh.c for DNS-over-HTTPS.

See Transfer engine for the full state walk.

3. The connection filter stack

Once a connection is up, every byte going to or from the network passes through a chain of connection filters (lib/cfilters.c, lib/cfilters.h). A filter has do_connect, cntrl, send, and recv callbacks, and each filter has zero or one filter beneath it. From the protocol handler's perspective the chain looks like a single byte stream; underneath it might be:

http.c → cfilter[HTTP/2] → cfilter[TLS=OpenSSL] → cfilter[HAProxy] → cfilter[socket]

This is how curl supports HTTPS-over-HTTPS-proxy, HTTP/3 (QUIC) on top of UDP, HTTP/2 multiplexing, and TLS-on-TLS without any of the protocol handlers needing to know about it. New transports are added by writing a new filter type and inserting it into the chain. See Connection filters.

Protocol handlers

Each supported scheme exports a const struct Curl_protocol (lib/protocol.h) describing its scheme, default port, capabilities, and per-state callbacks (do_it, done, connecting, doing, connect_it, disconnect, etc.). Examples: Curl_protocol_http in lib/http.c, Curl_protocol_ftp in lib/ftp.c, Curl_protocol_smtp in lib/smtp.c. The transfer engine looks up the handler by URL scheme and calls into it from each transfer state.

Build systems

curl supports two parallel build systems and they are kept feature-parity:

  • GNU Autotools: configure.ac (162k lines) + acinclude.m4 + Makefile.am files. The classic POSIX flow.
  • CMake: CMakeLists.txt (92k lines) + helpers under CMake/. Required for Windows, increasingly used elsewhere.

Both produce the same libcurl artifact and the same curl binary; the workflow files under .github/workflows/ exercise both. A dedicated configure-vs-cmake.yml workflow guards against drift.

Where each subsystem lives

Subsystem Source Wiki page
Command-line tool src/ apps/curl
libcurl public headers include/curl/ libraries/libcurl
Internal portability helpers lib/curlx/ libraries/curlx
Multi state machine lib/multi.c, lib/multiif.h systems/transfer-engine
Connection cache + setup lib/conncache.c, lib/connect.c, lib/url.c systems/connection-management
Connection filters lib/cfilters.c, lib/cf-*.c systems/connection-filters
DNS resolution lib/dnscache.c, lib/hostip*.c, lib/asyn-*.c, lib/doh.c systems/dns-resolution
Protocol handler registry lib/protocol.c, lib/protocol.h systems/protocol-handlers
TLS backends lib/vtls/ features/tls-backends
HTTP/2 + HTTP/3 lib/http2.c, lib/vquic/ features/http2-http3
Authentication lib/vauth/, lib/http_negotiate.c, lib/http_ntlm.c, lib/http_aws_sigv4.c features/authentication
SSH lib/vssh/ features/ssh
Cookies, HSTS, alt-svc lib/cookie.c, lib/hsts.c, lib/altsvc.c features/cookies-and-state
Proxies (HTTP/SOCKS) lib/cf-h1-proxy.c, lib/cf-h2-proxy.c, lib/socks*.c features/proxies
WebSockets lib/ws.c features/websockets

A typical HTTPS request

To make the architecture concrete, here is what happens when an application calls curl_easy_perform() against https://example.com/:

sequenceDiagram
    participant App
    participant Easy as easy.c
    participant Multi as multi.c
    participant DNS as dnscache.c
    participant Conn as conncache.c
    participant CF as cfilters
    participant TLS as vtls/openssl.c
    participant HTTP as http.c

    App->>Easy: curl_easy_perform()
    Easy->>Multi: curl_multi_add_handle + loop
    Multi->>Multi: SETUP → CONNECT
    Multi->>DNS: Curl_resolv()
    DNS-->>Multi: addresses
    Multi->>Conn: existing reusable conn?
    Conn-->>Multi: no
    Multi->>CF: build filter chain socket→tls→h2
    CF->>TLS: SSL_connect()
    TLS-->>CF: handshake done
    Multi->>HTTP: do_it() — write headers
    HTTP->>CF: send request
    CF->>TLS: encrypt + write
    TLS-->>CF: bytes flushed
    HTTP->>CF: recv() response
    CF-->>HTTP: bytes
    HTTP->>Multi: DONE
    Multi-->>Easy: completed
    Easy-->>App: CURLE_OK

The same pipeline handles http://, ftp://, smtp://, etc. — only the protocol handler and the contents of the filter chain change.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Architecture – curl wiki | Factory