http.c: allow custom TCP keepalive behavior via config

curl supports a few options to control when and how often it should
instruct the OS to send TCP keepalives, like KEEPIDLE, KEEPINTVL, and
KEEPCNT. Until this point, there hasn't been a way for users to change
what values are used for these options, forcing them to rely on curl's
defaults.

But we do unconditionally enable TCP keepalives without giving users an
ability to tweak any fine-grained parameters. Ordinarily this isn't a
problem, particularly for users that have fast-enough connections,
and/or are talking to a server that has generous or nonexistent
thresholds for killing a connection it hasn't heard from in a while.

But it can present a problem when one or both of those assumptions fail.
For instance, I can reliably get an in-progress clone to be killed from
the remote end when cloning from some forges while using trickle to
limit my clone's bandwidth.

For those users and others who wish to more finely tune the OS's
keepalive behavior, expose configuration and environment variables which
allow setting curl's KEEPIDLE, KEEPINTVL, and KEEPCNT options.

Note that while KEEPIDLE and KEEPINTVL were added in curl 7.25.0,
KEEPCNT was added much more recently in curl 8.9.0. Per f7c094060c
(git-curl-compat: remove check for curl 7.25.0, 2024-10-23), both
KEEPIDLE and KEEPINTVL are set unconditionally. But since we may be
compiled with a curl that isn't as new as 8.9.0, only set KEEPCNT when
we have CURLOPT_TCP_KEEPCNT to begin with.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Acked-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
maint
Taylor Blau 2025-03-19 18:23:56 -04:00 committed by Junio C Hamano
parent bfdd2591b0
commit 46e6f9af3e
3 changed files with 57 additions and 1 deletions

View File

@ -296,6 +296,24 @@ http.lowSpeedLimit, http.lowSpeedTime::
Can be overridden by the `GIT_HTTP_LOW_SPEED_LIMIT` and
`GIT_HTTP_LOW_SPEED_TIME` environment variables.

http.keepAliveIdle::
Specifies how long in seconds to wait on an idle connection
before sending TCP keepalive probes (if supported by the OS). If
unset, curl's default value is used. Can be overridden by the
`GIT_HTTP_KEEPALIVE_IDLE` environment variable.

http.keepAliveInterval::
Specifies how long in seconds to wait between TCP keepalive
probes (if supported by the OS). If unset, curl's default value
is used. Can be overridden by the `GIT_HTTP_KEEPALIVE_INTERVAL`
environment variable.

http.keepAliveCount::
Specifies how many TCP keepalive probes to send before giving up
and terminating the connection (if supported by the OS). If
unset, curl's default value is used. Can be overridden by the
`GIT_HTTP_KEEPALIVE_COUNT` environment variable.

http.noEPSV::
A boolean which disables using of EPSV ftp command by curl.
This can be helpful with some "poor" ftp servers which don't

View File

@ -45,4 +45,11 @@
#define GIT_CURL_HAVE_CURLOPT_PROTOCOLS_STR 1
#endif

/**
* CURLOPT_TCP_KEEPCNT was added in 8.9.0, released in July, 2024.
*/
#if LIBCURL_VERSION_NUM >= 0x080900
#define GIT_CURL_HAVE_CURLOPT_TCP_KEEPCNT
#endif

#endif

33
http.c
View File

@ -104,6 +104,10 @@ static struct {
};
#endif

static long curl_tcp_keepidle = -1;
static long curl_tcp_keepintvl = -1;
static long curl_tcp_keepcnt = -1;

enum proactive_auth {
PROACTIVE_AUTH_NONE = 0,
PROACTIVE_AUTH_IF_CREDENTIALS,
@ -557,6 +561,19 @@ static int http_options(const char *var, const char *value,
return 0;
}

if (!strcmp("http.keepaliveidle", var)) {
curl_tcp_keepidle = git_config_int(var, value, ctx->kvi);
return 0;
}
if (!strcmp("http.keepaliveinterval", var)) {
curl_tcp_keepintvl = git_config_int(var, value, ctx->kvi);
return 0;
}
if (!strcmp("http.keepalivecount", var)) {
curl_tcp_keepcnt = git_config_int(var, value, ctx->kvi);
return 0;
}

/* Fall back on the default ones */
return git_default_config(var, value, ctx, data);
}
@ -704,7 +721,6 @@ static int has_proxy_cert_password(void)
return 1;
}


/* Return 1 if redactions have been made, 0 otherwise. */
static int redact_sensitive_header(struct strbuf *header, size_t offset)
{
@ -1240,6 +1256,17 @@ static CURL *get_curl_handle(void)

curl_easy_setopt(result, CURLOPT_TCP_KEEPALIVE, 1);

if (curl_tcp_keepidle > -1)
curl_easy_setopt(result, CURLOPT_TCP_KEEPIDLE,
curl_tcp_keepidle);
if (curl_tcp_keepintvl > -1)
curl_easy_setopt(result, CURLOPT_TCP_KEEPINTVL,
curl_tcp_keepintvl);
#ifdef GIT_CURL_HAVE_CURLOPT_TCP_KEEPCNT
if (curl_tcp_keepcnt > -1)
curl_easy_setopt(result, CURLOPT_TCP_KEEPCNT, curl_tcp_keepcnt);
#endif

return result;
}

@ -1382,6 +1409,10 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
ssl_cert_password_required = 1;
}

set_long_from_env(&curl_tcp_keepidle, "GIT_TCP_KEEPIDLE");
set_long_from_env(&curl_tcp_keepintvl, "GIT_TCP_KEEPINTVL");
set_long_from_env(&curl_tcp_keepcnt, "GIT_TCP_KEEPCNT");

curl_default = get_curl_handle();
}