summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2021-03-30 18:01:27 +0200
committerDaniel Stenberg <daniel@haxx.se>2021-04-22 11:12:00 +0200
commit2b6ef0ee707b795a1b2f52693fb1f036f9bc9001 (patch)
treeac3a647aa795a35e7560a60b82980e5cd896591c
parent19ea52da4df3c3ebc399ae25e705c7a8b5d45d95 (diff)
downloadcurl-bagder/timeout-per-addr.tar.gz
connect: add CURLOPT_IPTIMEOUT_MSbagder/timeout-per-addr
Allow applications to specify the timeout per IP address connect attempt instead of letting libcurl do its "halfing" method. Closes #6814
-rw-r--r--docs/libcurl/curl_easy_setopt.32
-rw-r--r--docs/libcurl/opts/CURLOPT_CONNECTTIMEOUT.33
-rw-r--r--docs/libcurl/opts/CURLOPT_CONNECTTIMEOUT_MS.33
-rw-r--r--docs/libcurl/opts/CURLOPT_IPTIMEOUT_MS.399
-rw-r--r--docs/libcurl/opts/Makefile.inc1
-rw-r--r--docs/libcurl/symbols-in-versions1
-rw-r--r--include/curl/curl.h3
-rw-r--r--lib/connect.c26
-rw-r--r--lib/easyoptions.c2
-rw-r--r--lib/setopt.c7
-rw-r--r--lib/urldata.h1
11 files changed, 141 insertions, 7 deletions
diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
index 62d4bee2f..0d5116eb4 100644
--- a/docs/libcurl/curl_easy_setopt.3
+++ b/docs/libcurl/curl_easy_setopt.3
@@ -493,6 +493,8 @@ Limit the age of connections for reuse. See \fICURLOPT_MAXAGE_CONN(3)\fP
Timeout for the connection phase. See \fICURLOPT_CONNECTTIMEOUT(3)\fP
.IP CURLOPT_CONNECTTIMEOUT_MS
Millisecond timeout for the connection phase. See \fICURLOPT_CONNECTTIMEOUT_MS(3)\fP
+.IP CURLOPT_IPTIMEOUT_MS
+Millisecond timeout per address. See \fICURLOPT_IPTIMEOUT_MS(3)\fP
.IP CURLOPT_IPRESOLVE
IP version to resolve to. See \fICURLOPT_IPRESOLVE(3)\fP
.IP CURLOPT_CONNECT_ONLY
diff --git a/docs/libcurl/opts/CURLOPT_CONNECTTIMEOUT.3 b/docs/libcurl/opts/CURLOPT_CONNECTTIMEOUT.3
index ba146c295..11b844441 100644
--- a/docs/libcurl/opts/CURLOPT_CONNECTTIMEOUT.3
+++ b/docs/libcurl/opts/CURLOPT_CONNECTTIMEOUT.3
@@ -5,7 +5,7 @@
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
-.\" * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
.\" *
.\" * This software is licensed as described in the file COPYING, which
.\" * you should have received as part of this distribution. The terms
@@ -63,3 +63,4 @@ value or a value that when converted to milliseconds is too large.
.SH "SEE ALSO"
.BR CURLOPT_CONNECTTIMEOUT_MS "(3), "
.BR CURLOPT_TIMEOUT "(3), " CURLOPT_LOW_SPEED_LIMIT "(3), "
+.BR CURLOPT_IPTIMEOUT_MS "(3), "
diff --git a/docs/libcurl/opts/CURLOPT_CONNECTTIMEOUT_MS.3 b/docs/libcurl/opts/CURLOPT_CONNECTTIMEOUT_MS.3
index 9ae6fe6a6..733c6b6e5 100644
--- a/docs/libcurl/opts/CURLOPT_CONNECTTIMEOUT_MS.3
+++ b/docs/libcurl/opts/CURLOPT_CONNECTTIMEOUT_MS.3
@@ -5,7 +5,7 @@
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
-.\" * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
.\" *
.\" * This software is licensed as described in the file COPYING, which
.\" * you should have received as part of this distribution. The terms
@@ -62,3 +62,4 @@ Returns CURLE_OK
.SH "SEE ALSO"
.BR CURLOPT_CONNECTTIMEOUT "(3), "
.BR CURLOPT_TIMEOUT "(3), " CURLOPT_LOW_SPEED_LIMIT "(3), "
+.BR CURLOPT_IPTIMEOUT_MS "(3), "
diff --git a/docs/libcurl/opts/CURLOPT_IPTIMEOUT_MS.3 b/docs/libcurl/opts/CURLOPT_IPTIMEOUT_MS.3
new file mode 100644
index 000000000..60f6c901c
--- /dev/null
+++ b/docs/libcurl/opts/CURLOPT_IPTIMEOUT_MS.3
@@ -0,0 +1,99 @@
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * Project ___| | | | _ \| |
+.\" * / __| | | | |_) | |
+.\" * | (__| |_| | _ <| |___
+.\" * \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at https://curl.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_IPTIMEOUT_MS 3 "30 Mar 2021" "libcurl 7.77.0" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_IPTIMEOUT_MS \- connection timeout per address
+.SH SYNOPSIS
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_IPTIMEOUT_MS, long timeout);
+.SH DESCRIPTION
+Pass a long. It should contain the maximum time in milliseconds that you allow
+a connect attempt to a single IP address to take.
+
+When \fICURLOPT_IPTIMEOUT_MS(3)\fP is at 0, libcurl allows half the remaining
+time for each new connect attempt if there are more addresses to try - and it
+tries both IPv4 and IPv6 in parallel. See the connect description below.
+
+When \fICURLOPT_IPTIMEOUT_MS(3)\fP is set to a non-zero value, this is the
+maximum time allowed for each individual connect to an IP address attempt.
+Independently of the number of addresses libcurl will try.
+
+The \fICURLOPT_CONNECTTIMEOUT_MS(3)\fP timeout still sets the limit for the
+maximum time the entire connection phase is allowed to take and
+\fICURLOPT_TIMEOUT_MS(3)\fP sets the limit for the entire transfer.
+.SH "CONNECT TIMEOUT HALFING"
+This "half method" is used when \fICURLOPT_IPTIMEOUT_MS(3)\fP is not set.
+
+libcurl resolves the host name and gets a set of addresses back. "example.com"
+gets converted into let's say 3 IPv4 addresses and 2 IPv6 addresses. If
+\fICURLOPT_CONNECTTIMEOUT_MS(3)\fP sets the timeout to 10 seconds, libcurl
+will first allow 5 seconds (half of 10) to complete both an IPv4 and an IPv6
+connect attempt. The first to complete a connection wins.
+
+If any of them fails early, the next address in line will be attempted at
+once. If they instead both timeout after the 5 seconds, libcurl will move on
+to try the next IPv4 and IPv6 addresses and since there are no more IPv6
+address in the list will now allow the full 5 remaining seconds for that but
+as there more IPv4 addresses it will only give 2.5 seconds (half of the
+remaining 5) for that connect attempt.
+.SH "CONNECT TIMEOUT PER ADDRESS"
+This timeout method is used when \fICURLOPT_IPTIMEOUT_MS(3)\fP is set to a
+non-zero value.
+
+libcurl resolves the host name and gets a set of addresses back. "example.com"
+gets converted into let's say 3 IPv4 addresses and 2 IPv6 addresses. If
+\fICURLOPT_CONNECTTIMEOUT_MS(3)\fP sets the timeout to 10 seconds and
+\fICURLOPT_IPTIMEOUT_MS(3)\fP is set to 4 seconds, libcurl will first allow 4
+seconds to complete both an IPv4 and an IPv6 connect attempt. The first to
+complete a connection wins.
+
+If any of them fails early, the next address in line will be attempted at
+once. If they instead timeout after the 4 seconds, libcurl will move on to try
+more IPv4 and IPv6 addresses and again give them 4 seconds each to
+complete. This continues until it runs out of adresses or the total timeout is
+reached. If all addresses timeout, the third IPv4 address will only get 2
+seconds to complete since the first two got 4 each.
+.SH DEFAULT
+0
+.SH PROTOCOLS
+All
+.SH EXAMPLE
+.nf
+CURL *curl = curl_easy_init();
+if(curl) {
+ curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
+
+ /* spend only 500 milliseconds per connect attempt */
+ curl_easy_setopt(curl, CURLOPT_IPTIMEOUT_MS, 500L);
+
+ curl_easy_perform(curl);
+}
+.fi
+.SH AVAILABILITY
+Added in 7.77.0
+.SH RETURN VALUE
+Returns CURLE_OK
+.SH "SEE ALSO"
+.BR CURLOPT_CONNECTTIMEOUT_MS "(3), "
+.BR CURLOPT_TIMEOUT_MS "(3), " CURLOPT_LOW_SPEED_LIMIT "(3), "
diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc
index 69e241862..af2ef522c 100644
--- a/docs/libcurl/opts/Makefile.inc
+++ b/docs/libcurl/opts/Makefile.inc
@@ -366,6 +366,7 @@ man_MANS = \
CURLOPT_TIMECONDITION.3 \
CURLOPT_TIMEOUT.3 \
CURLOPT_TIMEOUT_MS.3 \
+ CURLOPT_IPTIMEOUT_MS.3 \
CURLOPT_TIMEVALUE.3 \
CURLOPT_TIMEVALUE_LARGE.3 \
CURLOPT_TLS13_CIPHERS.3 \
diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
index 5ff3a3503..ca00514bf 100644
--- a/docs/libcurl/symbols-in-versions
+++ b/docs/libcurl/symbols-in-versions
@@ -653,6 +653,7 @@ CURLOPT_TFTP_NO_OPTIONS 7.48.0
CURLOPT_TIMECONDITION 7.1
CURLOPT_TIMEOUT 7.1
CURLOPT_TIMEOUT_MS 7.16.2
+CURLOPT_IPTIMEOUT_MS 7.77.0
CURLOPT_TIMEVALUE 7.1
CURLOPT_TIMEVALUE_LARGE 7.59.0
CURLOPT_TLS13_CIPHERS 7.61.0
diff --git a/include/curl/curl.h b/include/curl/curl.h
index bed8068b0..e68c651df 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -2088,6 +2088,9 @@ typedef enum {
/* Same as CURLOPT_SSL_VERIFYSTATUS but for DOH (DNS-over-HTTPS) servers. */
CURLOPT(CURLOPT_DOH_SSL_VERIFYSTATUS, CURLOPTTYPE_LONG, 308),
+ /* Connection timeout per address attempt in milliseconds */
+ CURLOPT(CURLOPT_IPTIMEOUT_MS, CURLOPTTYPE_LONG, 309),
+
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;
diff --git a/lib/connect.c b/lib/connect.c
index 824ced31e..099b9fb36 100644
--- a/lib/connect.c
+++ b/lib/connect.c
@@ -845,6 +845,24 @@ static void post_SOCKS(struct Curl_easy *data,
}
/*
+ * timeout_per_addr() returns the time to spend on this connect attempt.
+ *
+ * If there are more addresses to try, this function returns half of the
+ * available time to allow furter attempts to more addresses rather than to
+ * spend the entire time on the current.
+ */
+static timediff_t timeout_per_addr(struct Curl_easy *data,
+ timediff_t timeout_ms, bool another)
+{
+ if(data->set.timeout_per_addr) {
+ /* if the total timeout left is smaller, return that */
+ return (data->set.timeout_per_addr < timeout_ms) ?
+ data->set.timeout_per_addr : timeout_ms;
+ }
+ return another ? timeout_ms / 2 : timeout_ms;
+}
+
+/*
* Curl_is_connected() checks if the socket has connected.
*/
@@ -994,8 +1012,8 @@ CURLcode Curl_is_connected(struct Curl_easy *data,
Curl_strerror(error, buffer, sizeof(buffer)));
#endif
- conn->timeoutms_per_addr[i] = conn->tempaddr[i]->ai_next == NULL ?
- allow : allow / 2;
+ conn->timeoutms_per_addr[i] =
+ timeout_per_addr(data, allow, conn->tempaddr[i]->ai_next != NULL);
ainext(conn, i, TRUE);
status = trynextip(data, conn, sockindex, i);
if((status != CURLE_COULDNT_CONNECT) ||
@@ -1363,9 +1381,9 @@ CURLcode Curl_connecthost(struct Curl_easy *data,
/* Max time for the next connection attempt */
conn->timeoutms_per_addr[0] =
- conn->tempaddr[0]->ai_next == NULL ? timeout_ms : timeout_ms / 2;
+ timeout_per_addr(data, timeout_ms, conn->tempaddr[0]->ai_next != NULL);
conn->timeoutms_per_addr[1] =
- conn->tempaddr[1]->ai_next == NULL ? timeout_ms : timeout_ms / 2;
+ timeout_per_addr(data, timeout_ms, conn->tempaddr[1]->ai_next != NULL);
conn->tempfamily[0] = conn->tempaddr[0]?
conn->tempaddr[0]->ai_family:0;
diff --git a/lib/easyoptions.c b/lib/easyoptions.c
index db8337b04..2ee742c7e 100644
--- a/lib/easyoptions.c
+++ b/lib/easyoptions.c
@@ -352,6 +352,6 @@ struct curl_easyoption Curl_easyopts[] = {
*/
int Curl_easyopts_check(void)
{
- return ((CURLOPT_LASTENTRY%10000) != (308 + 1));
+ return ((CURLOPT_LASTENTRY%10000) != (309 + 1));
}
#endif
diff --git a/lib/setopt.c b/lib/setopt.c
index 55360cba1..59253d35c 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -1409,6 +1409,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
data->set.connecttimeout = arg;
break;
+ case CURLOPT_IPTIMEOUT_MS:
+ arg = va_arg(param, long);
+ if(arg < 0)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ data->set.timeout_per_addr = arg;
+ break;
+
case CURLOPT_ACCEPTTIMEOUT_MS:
/*
* The maximum time you allow curl to wait for server connect
diff --git a/lib/urldata.h b/lib/urldata.h
index b7b17e30c..b9e295209 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1674,6 +1674,7 @@ struct UserDefined {
void *ioctl_client; /* pointer to pass to the ioctl callback */
long timeout; /* in milliseconds, 0 means no timeout */
long connecttimeout; /* in milliseconds, 0 means no timeout */
+ long timeout_per_addr; /* in milliseconds, 0 means "remaining/2" */
long accepttimeout; /* in milliseconds, 0 means no timeout */
long happy_eyeballs_timeout; /* in milliseconds, 0 is a valid value */
long server_response_timeout; /* in milliseconds, 0 means no timeout */