Skip to content

For Explicit FTPS mode, libcurl incorrectly reports connect timing. #22587

Description

@GLaDOS-418

I did this

Here's a simple program that can reproduce this:

#include <curl/curl.h>
#include <cstdio>

void version();
void print_duration(CURL* curl);

int main(int argc, char **argv) {
    if (argc != 4) {
        std::fprintf(stderr, "Usage: %s URL USER PWD\n", argv[0]);
        return 1;
    }

    curl_global_init(CURL_GLOBAL_DEFAULT);

    version();

    CURL *curl = curl_easy_init();
    if (!curl) {
        curl_global_cleanup();
        return 1;
    }

    curl_easy_setopt(curl, CURLOPT_URL, argv[1]);
    curl_easy_setopt(curl, CURLOPT_USERNAME, argv[2]);
    curl_easy_setopt(curl, CURLOPT_PASSWORD, argv[3]);

    // explicit ftps: connect using ftp://, then issue AUTH TLS.
    curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);

    CURLcode rc = curl_easy_perform(curl);
    if (rc != CURLE_OK)
        std::fprintf(stderr, "curl: %s\n", curl_easy_strerror(rc));

    print_duration(curl);

    curl_easy_cleanup(curl);
    curl_global_cleanup();

    return 0;
}

void version() {
    const curl_version_info_data* const info = curl_version_info(CURLVERSION_NOW);

    std::fprintf(stderr,
        "\nlibcurl metadata:\n"
        "  version:     %s\n"
        "  host:        %s\n"
        "  ssl_version: %s\n",
        info->version ? info->version : "(none)",
        info->host ? info->host : "(none)",
        info->ssl_version ? info->ssl_version : "(none)");

    std::fprintf(stderr, "\n");
}

void print_duration(CURL* curl) {
    curl_off_t dns_us     = 0;
    curl_off_t connect_us = 0;
    curl_off_t tls_us     = 0;
    curl_off_t total_us   = 0;

    curl_easy_getinfo(curl, CURLINFO_NAMELOOKUP_TIME_T, &dns_us);
    curl_easy_getinfo(curl, CURLINFO_CONNECT_TIME_T,    &connect_us);
    curl_easy_getinfo(curl, CURLINFO_APPCONNECT_TIME_T, &tls_us);
    curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME_T,      &total_us);

    const curl_off_t dns_duration_us = dns_us;
    const curl_off_t connect_duration_us = connect_us - dns_us;
    const curl_off_t tls_duration_us = tls_us > connect_us ? tls_us - connect_us : 0;

    std::fprintf(stderr,
        "\nTimings:\n"
        "  DNS:     %.3f ms\n"
        "  Connect: %.3f ms\n"
        "  TLS:     %.3f ms\n"
        "  Total:   %.3f ms\n",
        dns_duration_us     / 1000.0,
        connect_duration_us / 1000.0,
        tls_duration_us     / 1000.0,
        total_us            / 1000.0);
}

Now I compiled this against two libcurl versions 8.20 and 7.61 and tested against a public FTPS server:

url: ftp://demo.wftpserver.com
uname/pwd: demo demo

libcurl v8.20

Here's what it reported with 8.20:

$ ./eftps.new ftp://demo.wftpserver.com demo demo                                                        

libcurl metadata:
  version:     8.20.0
  host:        x86_64-pc-linux-gnu
  ssl_version: OpenSSL/3.5.5

drwxr-xr-x 2 user group              0  Aug 14 16:29 upload
drwxr-xr-x 2 user group              0  Jun 18 05:42 download

Timings:
  DNS:     5.916 ms
  Connect: 260.026 ms
  TLS:     58.061 ms
  Total:   316.231 ms

which is wildly off from what was reported in wireshark (6ms):

Image

See this capture (remove the .txt from the end): eftps.new.pcapng.txt.

libcurl v7.61.1

Interestingly, when I tried out on a way older libcurl + openssl build, I did not see that issue:

$ ./eftps.old ftp://demo.wftpserver.com demo demo

libcurl metadata:
  version:     7.61.1
  host:        x86_64-redhat-linux-gnu
  ssl_version: OpenSSL/1.1.1k

drwxr-xr-x 2 user group              0  Aug 14 16:28 upload
drwxr-xr-x 2 user group              0  Jun 18 05:42 download

Timings:
  DNS:     2.504 ms
  Connect: 28.325 ms
  TLS:     731.917 ms
  Total:   698.976 ms

The wireshark timings for this are accurate enough (27ms):

Image

see the wireshark capture below (remove the .txt).

eftps.old.pcapng.txt

libcurl 8.18.0

I do have a bigger program that's compiled with 8.18.0 with openssl 3.6.x which has the same issue. But, it was consistently producing 2x the connect time at the time (or so I believed but, 8.20 breaks that assumption so please ignore the annotations in the screenshot below).

It's on the same platform, unfortunately I lost the wireshark file for that. Here's the screenshot:

Image

Note

  • both the wireshark captures are recorded using the command:
$ sudo tshark -i any -f 'tcp port 21' -w /tmp/eftps.pcapng
  • I did not find this deviation in Implicit FTPS (or Active/Passive FTP).
  • MY GUESS is that for some reason it's "accumulating" connect time over various operations. Maybe because Explicit FTPS establishes the TCP connection, does a request-response round (AUTH SSL/AUTH TLS) with the server and THEN it starts the TLS connection. This is (afaik) a unique behaviour to Explicit FTPS alone. Coupled with the 8.18.0 analysis, I thought that was double counting only.

Let me know if my understanding is incorrect or what am I doing wrong.

I expected the following

I expect the connect time to accurately report the TCP connection time for Explicit FTPS mode (closely matching the Wireshark).

curl/libcurl version

libcurl 8.20.0 + OpenSSL 3.5.5
libcurl 8.18.0 + OpenSSL 3.6.x

operating system

$ uname -a
Linux <host_name> 5.15.0-322.203.3.4.el8uek.x86_64 #2 SMP Mon Jul 13 18:18:42 PDT 2026 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/os-release
NAME="Oracle Linux Server"
VERSION="8.10"
ID="ol"
ID_LIKE="fedora"
VARIANT="Server"
VARIANT_ID="server"
VERSION_ID="8.10"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Oracle Linux Server 8.10"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:oracle:linux:8:10:server"
HOME_URL="https://linux.oracle.com/"
BUG_REPORT_URL="https://github.com/oracle/oracle-linux"

ORACLE_BUGZILLA_PRODUCT="Oracle Linux 8"
ORACLE_BUGZILLA_PRODUCT_VERSION=8.10
ORACLE_SUPPORT_PRODUCT="Oracle Linux"
ORACLE_SUPPORT_PRODUCT_VERSION=8.10

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions