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>

Taylor Blau committed Mar 19, 2025 at 18:23 UTC 46e6f9af3ec063529738f4b5b0b97c28c005c365
3 files changed +57 -1
Documentation/config/http.adoc
+18
@@ -296,6 +296,24 @@ http.lowSpeedLimit, http.lowSpeedTime::
296 Can be overridden by the `GIT_HTTP_LOW_SPEED_LIMIT` and
297 `GIT_HTTP_LOW_SPEED_TIME` environment variables.
298
299 +http.keepAliveIdle::
300 + Specifies how long in seconds to wait on an idle connection
301 + before sending TCP keepalive probes (if supported by the OS). If
302 + unset, curl's default value is used. Can be overridden by the
303 + `GIT_HTTP_KEEPALIVE_IDLE` environment variable.
304 +
305 +http.keepAliveInterval::
306 + Specifies how long in seconds to wait between TCP keepalive
307 + probes (if supported by the OS). If unset, curl's default value
308 + is used. Can be overridden by the `GIT_HTTP_KEEPALIVE_INTERVAL`
309 + environment variable.
310 +
311 +http.keepAliveCount::
312 + Specifies how many TCP keepalive probes to send before giving up
313 + and terminating the connection (if supported by the OS). If
314 + unset, curl's default value is used. Can be overridden by the
315 + `GIT_HTTP_KEEPALIVE_COUNT` environment variable.
316 +
317 http.noEPSV::
318 A boolean which disables using of EPSV ftp command by curl.
319 This can be helpful with some "poor" ftp servers which don't
git-curl-compat.h
+7
@@ -45,4 +45,11 @@
45 #define GIT_CURL_HAVE_CURLOPT_PROTOCOLS_STR 1
46 #endif
47
48 +/**
49 + * CURLOPT_TCP_KEEPCNT was added in 8.9.0, released in July, 2024.
50 + */
51 +#if LIBCURL_VERSION_NUM >= 0x080900
52 +#define GIT_CURL_HAVE_CURLOPT_TCP_KEEPCNT
53 +#endif
54 +
55 #endif
http.c
+32 -1
@@ -104,6 +104,10 @@ static struct {
104 };
105 #endif
106
107 +static long curl_tcp_keepidle = -1;
108 +static long curl_tcp_keepintvl = -1;
109 +static long curl_tcp_keepcnt = -1;
110 +
111 enum proactive_auth {
112 PROACTIVE_AUTH_NONE = 0,
113 PROACTIVE_AUTH_IF_CREDENTIALS,
@@ -557,6 +561,19 @@ static int http_options(const char *var, const char *value,
561 return 0;
562 }
563
564 + if (!strcmp("http.keepaliveidle", var)) {
565 + curl_tcp_keepidle = git_config_int(var, value, ctx->kvi);
566 + return 0;
567 + }
568 + if (!strcmp("http.keepaliveinterval", var)) {
569 + curl_tcp_keepintvl = git_config_int(var, value, ctx->kvi);
570 + return 0;
571 + }
572 + if (!strcmp("http.keepalivecount", var)) {
573 + curl_tcp_keepcnt = git_config_int(var, value, ctx->kvi);
574 + return 0;
575 + }
576 +
577 /* Fall back on the default ones */
578 return git_default_config(var, value, ctx, data);
579 }
@@ -704,7 +721,6 @@ static int has_proxy_cert_password(void)
721 return 1;
722 }
723
707 -
724 /* Return 1 if redactions have been made, 0 otherwise. */
725 static int redact_sensitive_header(struct strbuf *header, size_t offset)
726 {
@@ -1240,6 +1256,17 @@ static CURL *get_curl_handle(void)
1256
1257 curl_easy_setopt(result, CURLOPT_TCP_KEEPALIVE, 1);
1258
1259 + if (curl_tcp_keepidle > -1)
1260 + curl_easy_setopt(result, CURLOPT_TCP_KEEPIDLE,
1261 + curl_tcp_keepidle);
1262 + if (curl_tcp_keepintvl > -1)
1263 + curl_easy_setopt(result, CURLOPT_TCP_KEEPINTVL,
1264 + curl_tcp_keepintvl);
1265 +#ifdef GIT_CURL_HAVE_CURLOPT_TCP_KEEPCNT
1266 + if (curl_tcp_keepcnt > -1)
1267 + curl_easy_setopt(result, CURLOPT_TCP_KEEPCNT, curl_tcp_keepcnt);
1268 +#endif
1269 +
1270 return result;
1271 }
1272
@@ -1382,6 +1409,10 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
1409 ssl_cert_password_required = 1;
1410 }
1411
1412 + set_long_from_env(&curl_tcp_keepidle, "GIT_TCP_KEEPIDLE");
1413 + set_long_from_env(&curl_tcp_keepintvl, "GIT_TCP_KEEPINTVL");
1414 + set_long_from_env(&curl_tcp_keepcnt, "GIT_TCP_KEEPCNT");
1415 +
1416 curl_default = get_curl_handle();
1417 }
1418