From: Stefan Eissing Date: Fri, 13 Jun 2025 10:38:44 +0000 (+0200) Subject: multi: add CURLMOPT_NETWORK_CHANGED to signal network changed X-Git-Tag: curl-8_16_0~337 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=55c045c86338bfcc1de676c496f1db084b7410d4;p=thirdparty%2Fcurl.git multi: add CURLMOPT_NETWORK_CHANGED to signal network changed New multi option CURLMOPT_NETWORK_CHANGED with a long bitmask value: - CURLM_NWCOPT_CLEAR_CONNS: do not reuse existing connections, close all idle connections. - CURLM_NWCOPT_CLEAR_DNS: clear the multi's DNS cache. All other bits reserved for future extensions. Fixes #17225 Reported-by: ウさん Closes #17613 --- diff --git a/docs/libcurl/curl_multi_setopt.md b/docs/libcurl/curl_multi_setopt.md index 55cc11beac..e646eced62 100644 --- a/docs/libcurl/curl_multi_setopt.md +++ b/docs/libcurl/curl_multi_setopt.md @@ -68,6 +68,10 @@ CURLMOPT_MAX_HOST_CONNECTIONS(3) Max simultaneously open connections. See CURLMOPT_MAX_TOTAL_CONNECTIONS(3) +## CURLMOPT_NETWORK_CHANGED + +Signal that the network has changed. See CURLMOPT_NETWORK_CHANGED(3) + ## CURLMOPT_PIPELINING Enable HTTP multiplexing. See CURLMOPT_PIPELINING(3) diff --git a/docs/libcurl/opts/CURLMOPT_NETWORK_CHANGED.md b/docs/libcurl/opts/CURLMOPT_NETWORK_CHANGED.md new file mode 100644 index 0000000000..bcfd1cdd37 --- /dev/null +++ b/docs/libcurl/opts/CURLMOPT_NETWORK_CHANGED.md @@ -0,0 +1,80 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Title: CURLMOPT_NETWORK_CHANGED +Section: 3 +Source: libcurl +See-also: + - CURLOPT_FRESH_CONNECT (3) + - CURLOPT_FORBID_REUSE (3) +Protocol: + - All +Added-in: 8.16.0 +--- + +# NAME + +CURLMOPT_NETWORK_CHANGED - signal network changed + +# SYNOPSIS + +~~~c +#include + +CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_NETWORK_CHANGED, + long value); +~~~ + +# DESCRIPTION + +Pass a long with a bitmask to tell libcurl how the multi +handle should react. The following values in the mask are +defined. All bits not mentioned are reserved for future +extensions. + +This option can be set at any time and repeatedly. Each call only +affects the *currently* cached connections and DNS information. +Any connection created or DNS information added afterwards is +cached the usual way again. Phrasing it another way: the option is +not persisted but setting it serves as a "trigger" +to clear the caches. + +The call affects only the connection and DNS cache of the multi handle +itself and not the ones owned by SHARE handles. + +## CURLM_NWCOPT_CLEAR_CONNS + +No longer reuse any existing connection in the multi handle's +connection cache. This closes all connections that are not in use. +Ongoing transfers continue on the connections they operate on. + +## CURLM_NWCOPT_CLEAR_DNS + +Clear the multi handle's DNS cache. + +# DEFAULT + +0, which has no effect. + +# %PROTOCOLS% + +# EXAMPLE + +~~~c +int main(void) +{ + CURLM *m = curl_multi_init(); + /* do transfers on the multi handle */ + /* do not reuse existing connections */ + curl_multi_setopt(m, CURLMOPT_NETWORK_CHANGED, CURLM_NWCOPT_CLEAR_CONNS); +} +~~~ + +# %AVAILABILITY% + +# RETURN VALUE + +curl_multi_setopt(3) returns a CURLMcode indicating success or error. + +CURLM_OK (0) means everything was OK, non-zero means an error occurred, see +libcurl-errors(3). diff --git a/docs/libcurl/opts/CURLOPT_FORBID_REUSE.md b/docs/libcurl/opts/CURLOPT_FORBID_REUSE.md index 3fdcadc76b..729dcdc702 100644 --- a/docs/libcurl/opts/CURLOPT_FORBID_REUSE.md +++ b/docs/libcurl/opts/CURLOPT_FORBID_REUSE.md @@ -8,6 +8,7 @@ See-also: - CURLOPT_FRESH_CONNECT (3) - CURLOPT_MAXCONNECTS (3) - CURLOPT_MAXLIFETIME_CONN (3) + - CURLMOPT_NETWORK_CHANGED (3) Protocol: - All Added-in: 7.7 diff --git a/docs/libcurl/opts/CURLOPT_FRESH_CONNECT.md b/docs/libcurl/opts/CURLOPT_FRESH_CONNECT.md index a466b6938d..0198a344f4 100644 --- a/docs/libcurl/opts/CURLOPT_FRESH_CONNECT.md +++ b/docs/libcurl/opts/CURLOPT_FRESH_CONNECT.md @@ -10,6 +10,7 @@ See-also: - CURLOPT_FORBID_REUSE (3) - CURLOPT_MAXAGE_CONN (3) - CURLOPT_MAXLIFETIME_CONN (3) + - CURLMOPT_NETWORK_CHANGED (3) Added-in: 7.7 --- diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc index d62d369a66..1cf960e5ee 100644 --- a/docs/libcurl/opts/Makefile.inc +++ b/docs/libcurl/opts/Makefile.inc @@ -108,6 +108,7 @@ man_MANS = \ CURLMOPT_MAX_PIPELINE_LENGTH.3 \ CURLMOPT_MAX_TOTAL_CONNECTIONS.3 \ CURLMOPT_MAXCONNECTS.3 \ + CURLMOPT_NETWORK_CHANGED.3 \ CURLMOPT_PIPELINING.3 \ CURLMOPT_PIPELINING_SERVER_BL.3 \ CURLMOPT_PIPELINING_SITE_BL.3 \ diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 4603dd322c..6387ea3014 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -545,6 +545,8 @@ CURLM_BAD_SOCKET 7.15.4 CURLM_CALL_MULTI_PERFORM 7.9.6 CURLM_CALL_MULTI_SOCKET 7.15.5 CURLM_INTERNAL_ERROR 7.9.6 +CURLM_NWCOPT_CLEAR_CONNS 8.16.0 +CURLM_NWCOPT_CLEAR_DNS 8.16.0 CURLM_OK 7.9.6 CURLM_OUT_OF_MEMORY 7.9.6 CURLM_RECURSIVE_API_CALL 7.59.0 @@ -559,6 +561,7 @@ CURLMOPT_MAX_HOST_CONNECTIONS 7.30.0 CURLMOPT_MAX_PIPELINE_LENGTH 7.30.0 CURLMOPT_MAX_TOTAL_CONNECTIONS 7.30.0 CURLMOPT_MAXCONNECTS 7.16.3 +CURLMOPT_NETWORK_CHANGED 8.16.0 CURLMOPT_PIPELINING 7.16.0 CURLMOPT_PIPELINING_SERVER_BL 7.30.0 CURLMOPT_PIPELINING_SITE_BL 7.30.0 diff --git a/include/curl/multi.h b/include/curl/multi.h index aa1291001f..0fbea88707 100644 --- a/include/curl/multi.h +++ b/include/curl/multi.h @@ -395,9 +395,23 @@ typedef enum { /* maximum number of concurrent streams to support on a connection */ CURLOPT(CURLMOPT_MAX_CONCURRENT_STREAMS, CURLOPTTYPE_LONG, 16), + /* network has changed, adjust caches/connection reuse */ + CURLOPT(CURLMOPT_NETWORK_CHANGED, CURLOPTTYPE_LONG, 17), + CURLMOPT_LASTENTRY /* the last unused */ } CURLMoption; +/* Definition of bits for the CURLMOPT_NETWORK_CHANGED argument: */ + +/* - CURLM_NWCOPT_CLEAR_CONNS tells libcurl to prevent further reuse + of existing connections. Connections that are idle will be closed. + Ongoing transfers will continue with the connection they have. */ +#define CURLM_NWCOPT_CLEAR_CONNS (1L<<0) + +/* - CURLM_NWCOPT_CLEAR_DNS tells libcurl to prevent further reuse + of existing connections. Connections that are idle will be closed. + Ongoing transfers will continue with the connection they have. */ +#define CURLM_NWCOPT_CLEAR_DNS (1L<<0) /* * Name: curl_multi_setopt() diff --git a/lib/conncache.c b/lib/conncache.c index f77a746874..1393bb565b 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -709,7 +709,8 @@ static int cpool_reap_dead_cb(struct Curl_easy *data, struct connectdata *conn, void *param) { struct cpool_reaper_ctx *rctx = param; - if(Curl_conn_seems_dead(conn, data, &rctx->now)) { + if((!CONN_INUSE(conn) && conn->bits.no_reuse) || + Curl_conn_seems_dead(conn, data, &rctx->now)) { /* stop the iteration here, pass back the connection that was pruned */ Curl_conn_terminate(data, conn, FALSE); return 1; @@ -849,6 +850,40 @@ void Curl_cpool_do_locked(struct Curl_easy *data, cb(conn, data, cbdata); } +static int cpool_mark_stale(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + (void)data; + (void)param; + conn->bits.no_reuse = TRUE; + return 0; +} + +static int cpool_reap_no_reuse(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + (void)data; + (void)param; + if(!CONN_INUSE(conn) && conn->bits.no_reuse) { + Curl_conn_terminate(data, conn, FALSE); + return 1; + } + return 0; /* continue iteration */ +} + +void Curl_cpool_nw_changed(struct Curl_easy *data) +{ + struct cpool *cpool = cpool_get_instance(data); + + if(cpool) { + CPOOL_LOCK(cpool, data); + cpool_foreach(data, cpool, NULL, cpool_mark_stale); + while(cpool_foreach(data, cpool, NULL, cpool_reap_no_reuse)) + ; + CPOOL_UNLOCK(cpool, data); + } +} + #if 0 /* Useful for debugging the connection pool */ void Curl_cpool_print(struct cpool *cpool) diff --git a/lib/conncache.h b/lib/conncache.h index ac07111a7d..a5f133344f 100644 --- a/lib/conncache.h +++ b/lib/conncache.h @@ -163,4 +163,8 @@ void Curl_cpool_do_locked(struct Curl_easy *data, struct connectdata *conn, Curl_cpool_conn_do_cb *cb, void *cbdata); +/* Close all unused connections, prevent reuse of existing ones. */ +void Curl_cpool_nw_changed(struct Curl_easy *data); + + #endif /* HEADER_CURL_CONNCACHE_H */ diff --git a/lib/easy.c b/lib/easy.c index e79171a3ae..b526e6cff0 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1102,6 +1102,7 @@ void curl_easy_reset(CURL *d) data->progress.hide = TRUE; data->state.current_speed = -1; /* init to negative == impossible */ data->state.retrycount = 0; /* reset the retry counter */ + data->state.recent_conn_id = -1; /* clear remembered connection id */ /* zero out authentication data: */ memset(&data->state.authhost, 0, sizeof(struct auth)); diff --git a/lib/hostip.c b/lib/hostip.c index c1bc3d4633..95c2c62242 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -290,6 +290,16 @@ void Curl_dnscache_prune(struct Curl_easy *data) dnscache_unlock(data, dnscache); } +void Curl_dnscache_clear(struct Curl_easy *data) +{ + struct Curl_dnscache *dnscache = dnscache_get(data); + if(dnscache) { + dnscache_lock(data, dnscache); + Curl_hash_clean(&dnscache->entries); + dnscache_unlock(data, dnscache); + } +} + #ifdef USE_ALARM_TIMEOUT /* Beware this is a global and unique instance. This is used to store the return address that we can jump back to from inside a signal handler. This diff --git a/lib/hostip.h b/lib/hostip.h index cd3d957e1e..56c5e9cad1 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -130,6 +130,9 @@ void Curl_dnscache_destroy(struct Curl_dnscache *dns); /* prune old entries from the DNS cache */ void Curl_dnscache_prune(struct Curl_easy *data); +/* clear the DNS cache */ +void Curl_dnscache_clear(struct Curl_easy *data); + /* IPv4 threadsafe resolve function used for synch and asynch builds */ struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port); diff --git a/lib/multi.c b/lib/multi.c index 79d44796ec..194cf75d40 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -3235,6 +3235,16 @@ CURLMcode curl_multi_setopt(CURLM *m, multi->max_concurrent_streams = (unsigned int)streams; } break; + case CURLMOPT_NETWORK_CHANGED: { + long val = va_arg(param, long); + if(val & CURLM_NWCOPT_CLEAR_DNS) { + Curl_dnscache_clear(multi->admin); + } + if(val & CURLM_NWCOPT_CLEAR_CONNS) { + Curl_cpool_nw_changed(multi->admin); + } + break; + } default: res = CURLM_UNKNOWN_OPTION; break; diff --git a/lib/url.c b/lib/url.c index 8a808ef5fd..3a57be4464 100644 --- a/lib/url.c +++ b/lib/url.c @@ -843,7 +843,7 @@ static bool url_match_connect_config(struct connectdata *conn, struct url_conn_match *m) { /* connect-only or to-be-closed connections will not be reused */ - if(conn->connect_only || conn->bits.close) + if(conn->connect_only || conn->bits.close || conn->bits.no_reuse) return FALSE; /* ip_version must match */ diff --git a/lib/urldata.h b/lib/urldata.h index cae24966eb..8c3a85cdab 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -423,6 +423,7 @@ struct ConnectBits { BIT(parallel_connect); /* set TRUE when a parallel connect attempt has started (happy eyeballs) */ BIT(aborted); /* connection was aborted, e.g. in unclean state */ + BIT(no_reuse); /* connection should not be reused */ BIT(shutdown_handler); /* connection shutdown: handler shut down */ BIT(shutdown_filters); /* connection shutdown: filters shut down */ BIT(in_cpool); /* connection is kept in a connection pool */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index cc3ea533d5..31f61216e6 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -273,7 +273,7 @@ test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \ test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \ test3016 test3017 test3018 test3019 test3020 test3021 test3022 test3023 \ test3024 test3025 test3026 test3027 test3028 test3029 test3030 test3031 \ -test3032 \ +test3032 test3033 \ \ test3100 test3101 test3102 test3103 test3104 test3105 \ \ diff --git a/tests/data/test3033 b/tests/data/test3033 new file mode 100644 index 0000000000..8ba008b531 --- /dev/null +++ b/tests/data/test3033 @@ -0,0 +1,53 @@ + + + +curl_easy_setopt +connection reuse +libtest + + + +# +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 6 + +-foo- + + +[0] no network change +-foo- +[1] signal network change +-foo- +[2] no network change +-foo- + + + +# +# Client-side + + +http + + +CURLOPT_FRESH_CONNECT=2 + + +lib%TESTNUMBER + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# +# Verify data after the test has been "shot" + + +0 + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 8878ae85fa..ba402d903b 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -93,6 +93,6 @@ TESTS_C = \ lib2402.c lib2404.c lib2405.c \ lib2502.c \ lib2700.c \ - lib3010.c lib3025.c lib3026.c lib3027.c \ + lib3010.c lib3025.c lib3026.c lib3027.c lib3033.c \ lib3100.c lib3101.c lib3102.c lib3103.c lib3104.c lib3105.c \ lib3207.c lib3208.c diff --git a/tests/libtest/lib3033.c b/tests/libtest/lib3033.c new file mode 100644 index 0000000000..f0a64198c8 --- /dev/null +++ b/tests/libtest/lib3033.c @@ -0,0 +1,129 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "testtrace.h" +#include "testutil.h" +#include "memdebug.h" + + +static CURLcode t3033_req_test(CURLM *multi, CURL *easy, + char *url_3033, int index) +{ + CURLMsg *msg = NULL; + CURLcode res = CURLE_OK; + int still_running = 0; + + if(index == 1) { + curl_multi_setopt(multi, CURLMOPT_NETWORK_CHANGED, + CURLM_NWCOPT_CLEAR_CONNS); + curl_mprintf("[1] signal network change\n"); + } + else { + curl_mprintf("[%d] no network change\n", index); + } + + curl_easy_reset(easy); + curl_easy_setopt(easy, CURLOPT_URL, url_3033); + easy_setopt(easy, CURLOPT_DEBUGDATA, &libtest_debug_config); + easy_setopt(easy, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); + easy_setopt(easy, CURLOPT_VERBOSE, 1L); + + curl_multi_add_handle(multi, easy); + + do { + CURLMcode mres; + int num; + curl_multi_perform(multi, &still_running); + mres = curl_multi_wait(multi, NULL, 0, TEST_HANG_TIMEOUT, &num); + if(mres != CURLM_OK) { + curl_mfprintf(stderr, "curl_multi_wait() returned %d\n", mres); + res = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + } while(still_running); + + do { + long num_connects = 0L; + msg = curl_multi_info_read(multi, &still_running); + if(msg) { + if(msg->msg != CURLMSG_DONE) + continue; + + res = msg->data.result; + if(res != CURLE_OK) { + curl_mfprintf(stderr, "curl_multi_info_read() returned %d\n", res); + goto test_cleanup; + } + + curl_easy_getinfo(easy, CURLINFO_NUM_CONNECTS, &num_connects); + if(index == 1 && num_connects == 0) { + curl_mprintf("[1] should not reuse connection in pool\n"); + res = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + else if(index == 2 && num_connects) { + curl_mprintf("[2] should have reused connection from [1]\n"); + res = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + } + } while(msg); + +test_cleanup: + + curl_multi_remove_handle(multi, easy); + + return res; +} + +static CURLcode test_lib3033(char *URL) +{ + CURL *curl = NULL; + CURLM *multi = NULL; + CURLcode res = CURLE_OK; + + global_init(CURL_GLOBAL_ALL); + multi_init(multi); + easy_init(curl); + + libtest_debug_config.nohex = 1; + libtest_debug_config.tracetime = 1; + + res = t3033_req_test(multi, curl, URL, 0); + if(res != CURLE_OK) + goto test_cleanup; + res = t3033_req_test(multi, curl, URL, 1); + if(res != CURLE_OK) + goto test_cleanup; + res = t3033_req_test(multi, curl, URL, 2); + if(res != CURLE_OK) + goto test_cleanup; + +test_cleanup: + + curl_easy_cleanup(curl); + curl_multi_cleanup(multi); + curl_global_cleanup(); + + return res; /* return the final return code */ +}