1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 2012 - 2016, Linus Nielsen Feltzing, <[email protected]>
9 * Copyright (C) 2012 - 2022, Daniel Stenberg, <[email protected]>, et al.
10 *
11 * This software is licensed as described in the file COPYING, which
12 * you should have received as part of this distribution. The terms
13 * are also available at https://curl.se/docs/copyright.html.
14 *
15 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
16 * copies of the Software, and permit persons to whom the Software is
17 * furnished to do so, under the terms of the COPYING file.
18 *
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
21 *
22 * SPDX-License-Identifier: curl
23 *
24 ***************************************************************************/
25
26#include "curl_setup.h"
27
28#include <curl/curl.h>
29
30#include "urldata.h"
31#include "url.h"
32#include "progress.h"
33#include "multiif.h"
34#include "sendf.h"
35#include "conncache.h"
36#include "share.h"
37#include "sigpipe.h"
38#include "connect.h"
39#include "strcase.h"
40
41/* The last 3 #include files should be in this order */
42#include "curl_printf.h"
43#include "curl_memory.h"
44#include "memdebug.h"
45
46#define HASHKEY_SIZE 128
47
48static void conn_llist_dtor(void *user, void *element)
49{
50 struct connectdata *conn = element;
51 (void)user;
52 conn->bundle = NULL;
53}
54
55static CURLcode bundle_create(struct connectbundle **bundlep)
56{
57 DEBUGASSERT(*bundlep == NULL);
58 *bundlep = malloc(sizeof(struct connectbundle));
59 if(!*bundlep)
60 return CURLE_OUT_OF_MEMORY;
61
62 (*bundlep)->num_connections = 0;
63 (*bundlep)->multiuse = BUNDLE_UNKNOWN;
64
65 Curl_llist_init(&(*bundlep)->conn_list, (Curl_llist_dtor) conn_llist_dtor);
66 return CURLE_OK;
67}
68
69static void bundle_destroy(struct connectbundle *bundle)
70{
71 if(!bundle)
72 return;
73
74 Curl_llist_destroy(&bundle->conn_list, NULL);
75
76 free(bundle);
77}
78
79/* Add a connection to a bundle */
80static void bundle_add_conn(struct connectbundle *bundle,
81 struct connectdata *conn)
82{
83 Curl_llist_insert_next(&bundle->conn_list, bundle->conn_list.tail, conn,
84 &conn->bundle_node);
85 conn->bundle = bundle;
86 bundle->num_connections++;
87}
88
89/* Remove a connection from a bundle */
90static int bundle_remove_conn(struct connectbundle *bundle,
91 struct connectdata *conn)
92{
93 struct Curl_llist_element *curr;
94
95 curr = bundle->conn_list.head;
96 while(curr) {
97 if(curr->ptr == conn) {
98 Curl_llist_remove(&bundle->conn_list, curr, NULL);
99 bundle->num_connections--;
100 conn->bundle = NULL;
101 return 1; /* we removed a handle */
102 }
103 curr = curr->next;
104 }
105 DEBUGASSERT(0);
106 return 0;
107}
108
109static void free_bundle_hash_entry(void *freethis)
110{
111 struct connectbundle *b = (struct connectbundle *) freethis;
112
113 bundle_destroy(b);
114}
115
116int Curl_conncache_init(struct conncache *connc, int size)
117{
118 /* allocate a new easy handle to use when closing cached connections */
119 connc->closure_handle = curl_easy_init();
120 if(!connc->closure_handle)
121 return 1; /* bad */
122
123 Curl_hash_init(&connc->hash, size, Curl_hash_str,
124 Curl_str_key_compare, free_bundle_hash_entry);
125 connc->closure_handle->state.conn_cache = connc;
126
127 return 0; /* good */
128}
129
130void Curl_conncache_destroy(struct conncache *connc)
131{
132 if(connc)
133 Curl_hash_destroy(&connc->hash);
134}
135
136/* creates a key to find a bundle for this connection */
137static void hashkey(struct connectdata *conn, char *buf, size_t len)
138{
139 const char *hostname;
140 long port = conn->remote_port;
141 DEBUGASSERT(len >= HASHKEY_SIZE);
142#ifndef CURL_DISABLE_PROXY
143 if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
144 hostname = conn->http_proxy.host.name;
145 port = conn->port;
146 }
147 else
148#endif
149 if(conn->bits.conn_to_host)
150 hostname = conn->conn_to_host.name;
151 else
152 hostname = conn->host.name;
153
154 /* put the numbers first so that the hostname gets cut off if too long */
155#ifdef ENABLE_IPV6
156 msnprintf(buf, len, "%u/%ld/%s", conn->scope_id, port, hostname);
157#else
158 msnprintf(buf, len, "%ld/%s", port, hostname);
159#endif
160 Curl_strntolower(buf, buf, len);
161}
162
163/* Returns number of connections currently held in the connection cache.
164 Locks/unlocks the cache itself!
165*/
166size_t Curl_conncache_size(struct Curl_easy *data)
167{
168 size_t num;
169 CONNCACHE_LOCK(data);
170 num = data->state.conn_cache->num_conn;
171 CONNCACHE_UNLOCK(data);
172 return num;
173}
174
175/* Look up the bundle with all the connections to the same host this
176 connectdata struct is setup to use.
177
178 **NOTE**: When it returns, it holds the connection cache lock! */
179struct connectbundle *
180Curl_conncache_find_bundle(struct Curl_easy *data,
181 struct connectdata *conn,
182 struct conncache *connc)
183{
184 struct connectbundle *bundle = NULL;
185 CONNCACHE_LOCK(data);
186 if(connc) {
187 char key[HASHKEY_SIZE];
188 hashkey(conn, key, sizeof(key));
189 bundle = Curl_hash_pick(&connc->hash, key, strlen(key));
190 }
191
192 return bundle;
193}
194
195static void *conncache_add_bundle(struct conncache *connc,
196 char *key,
197 struct connectbundle *bundle)
198{
199 return Curl_hash_add(&connc->hash, key, strlen(key), bundle);
200}
201
202static void conncache_remove_bundle(struct conncache *connc,
203 struct connectbundle *bundle)
204{
205 struct Curl_hash_iterator iter;
206 struct Curl_hash_element *he;
207
208 if(!connc)
209 return;
210
211 Curl_hash_start_iterate(&connc->hash, &iter);
212
213 he = Curl_hash_next_element(&iter);
214 while(he) {
215 if(he->ptr == bundle) {
216 /* The bundle is destroyed by the hash destructor function,
217 free_bundle_hash_entry() */
218 Curl_hash_delete(&connc->hash, he->key, he->key_len);
219 return;
220 }
221
222 he = Curl_hash_next_element(&iter);
223 }
224}
225
226CURLcode Curl_conncache_add_conn(struct Curl_easy *data)
227{
228 CURLcode result = CURLE_OK;
229 struct connectbundle *bundle = NULL;
230 struct connectdata *conn = data->conn;
231 struct conncache *connc = data->state.conn_cache;
232 DEBUGASSERT(conn);
233
234 /* *find_bundle() locks the connection cache */
235 bundle = Curl_conncache_find_bundle(data, conn, data->state.conn_cache);
236 if(!bundle) {
237 char key[HASHKEY_SIZE];
238
239 result = bundle_create(&bundle);
240 if(result) {
241 goto unlock;
242 }
243
244 hashkey(conn, key, sizeof(key));
245
246 if(!conncache_add_bundle(data->state.conn_cache, key, bundle)) {
247 bundle_destroy(bundle);
248 result = CURLE_OUT_OF_MEMORY;
249 goto unlock;
250 }
251 }
252
253 bundle_add_conn(bundle, conn);
254 conn->connection_id = connc->next_connection_id++;
255 connc->num_conn++;
256
257 DEBUGF(infof(data, "Added connection %ld. "
258 "The cache now contains %zu members",
259 conn->connection_id, connc->num_conn));
260
261 unlock:
262 CONNCACHE_UNLOCK(data);
263
264 return result;
265}
266
267/*
268 * Removes the connectdata object from the connection cache, but the transfer
269 * still owns this connection.
270 *
271 * Pass TRUE/FALSE in the 'lock' argument depending on if the parent function
272 * already holds the lock or not.
273 */
274void Curl_conncache_remove_conn(struct Curl_easy *data,
275 struct connectdata *conn, bool lock)
276{
277 struct connectbundle *bundle = conn->bundle;
278 struct conncache *connc = data->state.conn_cache;
279
280 /* The bundle pointer can be NULL, since this function can be called
281 due to a failed connection attempt, before being added to a bundle */
282 if(bundle) {
283 if(lock) {
284 CONNCACHE_LOCK(data);
285 }
286 bundle_remove_conn(bundle, conn);
287 if(bundle->num_connections == 0)
288 conncache_remove_bundle(connc, bundle);
289 conn->bundle = NULL; /* removed from it */
290 if(connc) {
291 connc->num_conn--;
292 DEBUGF(infof(data, "The cache now contains %zu members",
293 connc->num_conn));
294 }
295 if(lock) {
296 CONNCACHE_UNLOCK(data);
297 }
298 }
299}
300
301/* This function iterates the entire connection cache and calls the function
302 func() with the connection pointer as the first argument and the supplied
303 'param' argument as the other.
304
305 The conncache lock is still held when the callback is called. It needs it,
306 so that it can safely continue traversing the lists once the callback
307 returns.
308
309 Returns 1 if the loop was aborted due to the callback's return code.
310
311 Return 0 from func() to continue the loop, return 1 to abort it.
312 */
313bool Curl_conncache_foreach(struct Curl_easy *data,
314 struct conncache *connc,
315 void *param,
316 int (*func)(struct Curl_easy *data,
317 struct connectdata *conn, void *param))
318{
319 struct Curl_hash_iterator iter;
320 struct Curl_llist_element *curr;
321 struct Curl_hash_element *he;
322
323 if(!connc)
324 return FALSE;
325
326 CONNCACHE_LOCK(data);
327 Curl_hash_start_iterate(&connc->hash, &iter);
328
329 he = Curl_hash_next_element(&iter);
330 while(he) {
331 struct connectbundle *bundle;
332
333 bundle = he->ptr;
334 he = Curl_hash_next_element(&iter);
335
336 curr = bundle->conn_list.head;
337 while(curr) {
338 /* Yes, we need to update curr before calling func(), because func()
339 might decide to remove the connection */
340 struct connectdata *conn = curr->ptr;
341 curr = curr->next;
342
343 if(1 == func(data, conn, param)) {
344 CONNCACHE_UNLOCK(data);
345 return TRUE;
346 }
347 }
348 }
349 CONNCACHE_UNLOCK(data);
350 return FALSE;
351}
352
353/* Return the first connection found in the cache. Used when closing all
354 connections.
355
356 NOTE: no locking is done here as this is presumably only done when cleaning
357 up a cache!
358*/
359static struct connectdata *
360conncache_find_first_connection(struct conncache *connc)
361{
362 struct Curl_hash_iterator iter;
363 struct Curl_hash_element *he;
364 struct connectbundle *bundle;
365
366 Curl_hash_start_iterate(&connc->hash, &iter);
367
368 he = Curl_hash_next_element(&iter);
369 while(he) {
370 struct Curl_llist_element *curr;
371 bundle = he->ptr;
372
373 curr = bundle->conn_list.head;
374 if(curr) {
375 return curr->ptr;
376 }
377
378 he = Curl_hash_next_element(&iter);
379 }
380
381 return NULL;
382}
383
384/*
385 * Give ownership of a connection back to the connection cache. Might
386 * disconnect the oldest existing in there to make space.
387 *
388 * Return TRUE if stored, FALSE if closed.
389 */
390bool Curl_conncache_return_conn(struct Curl_easy *data,
391 struct connectdata *conn)
392{
393 /* data->multi->maxconnects can be negative, deal with it. */
394 size_t maxconnects =
395 (data->multi->maxconnects < 0) ? data->multi->num_easy * 4:
396 data->multi->maxconnects;
397 struct connectdata *conn_candidate = NULL;
398
399 conn->lastused = Curl_now(); /* it was used up until now */
400 if(maxconnects > 0 &&
401 Curl_conncache_size(data) > maxconnects) {
402 infof(data, "Connection cache is full, closing the oldest one");
403
404 conn_candidate = Curl_conncache_extract_oldest(data);
405 if(conn_candidate) {
406 /* the winner gets the honour of being disconnected */
407 Curl_disconnect(data, conn_candidate, /* dead_connection */ FALSE);
408 }
409 }
410
411 return (conn_candidate == conn) ? FALSE : TRUE;
412
413}
414
415/*
416 * This function finds the connection in the connection bundle that has been
417 * unused for the longest time.
418 *
419 * Does not lock the connection cache!
420 *
421 * Returns the pointer to the oldest idle connection, or NULL if none was
422 * found.
423 */
424struct connectdata *
425Curl_conncache_extract_bundle(struct Curl_easy *data,
426 struct connectbundle *bundle)
427{
428 struct Curl_llist_element *curr;
429 timediff_t highscore = -1;
430 timediff_t score;
431 struct curltime now;
432 struct connectdata *conn_candidate = NULL;
433 struct connectdata *conn;
434
435 (void)data;
436
437 now = Curl_now();
438
439 curr = bundle->conn_list.head;
440 while(curr) {
441 conn = curr->ptr;
442
443 if(!CONN_INUSE(conn)) {
444 /* Set higher score for the age passed since the connection was used */
445 score = Curl_timediff(now, conn->lastused);
446
447 if(score > highscore) {
448 highscore = score;
449 conn_candidate = conn;
450 }
451 }
452 curr = curr->next;
453 }
454 if(conn_candidate) {
455 /* remove it to prevent another thread from nicking it */
456 bundle_remove_conn(bundle, conn_candidate);
457 data->state.conn_cache->num_conn--;
458 DEBUGF(infof(data, "The cache now contains %zu members",
459 data->state.conn_cache->num_conn));
460 }
461
462 return conn_candidate;
463}
464
465/*
466 * This function finds the connection in the connection cache that has been
467 * unused for the longest time and extracts that from the bundle.
468 *
469 * Returns the pointer to the connection, or NULL if none was found.
470 */
471struct connectdata *
472Curl_conncache_extract_oldest(struct Curl_easy *data)
473{
474 struct conncache *connc = data->state.conn_cache;
475 struct Curl_hash_iterator iter;
476 struct Curl_llist_element *curr;
477 struct Curl_hash_element *he;
478 timediff_t highscore =- 1;
479 timediff_t score;
480 struct curltime now;
481 struct connectdata *conn_candidate = NULL;
482 struct connectbundle *bundle;
483 struct connectbundle *bundle_candidate = NULL;
484
485 now = Curl_now();
486
487 CONNCACHE_LOCK(data);
488 Curl_hash_start_iterate(&connc->hash, &iter);
489
490 he = Curl_hash_next_element(&iter);
491 while(he) {
492 struct connectdata *conn;
493
494 bundle = he->ptr;
495
496 curr = bundle->conn_list.head;
497 while(curr) {
498 conn = curr->ptr;
499
500 if(!CONN_INUSE(conn) && !conn->bits.close &&
501 !conn->bits.connect_only) {
502 /* Set higher score for the age passed since the connection was used */
503 score = Curl_timediff(now, conn->lastused);
504
505 if(score > highscore) {
506 highscore = score;
507 conn_candidate = conn;
508 bundle_candidate = bundle;
509 }
510 }
511 curr = curr->next;
512 }
513
514 he = Curl_hash_next_element(&iter);
515 }
516 if(conn_candidate) {
517 /* remove it to prevent another thread from nicking it */
518 bundle_remove_conn(bundle_candidate, conn_candidate);
519 connc->num_conn--;
520 DEBUGF(infof(data, "The cache now contains %zu members",
521 connc->num_conn));
522 }
523 CONNCACHE_UNLOCK(data);
524
525 return conn_candidate;
526}
527
528void Curl_conncache_close_all_connections(struct conncache *connc)
529{
530 struct connectdata *conn;
531 char buffer[READBUFFER_MIN + 1];
532 SIGPIPE_VARIABLE(pipe_st);
533 if(!connc->closure_handle)
534 return;
535 connc->closure_handle->state.buffer = buffer;
536 connc->closure_handle->set.buffer_size = READBUFFER_MIN;
537
538 conn = conncache_find_first_connection(connc);
539 while(conn) {
540 sigpipe_ignore(connc->closure_handle, &pipe_st);
541 /* This will remove the connection from the cache */
542 connclose(conn, "kill all");
543 Curl_conncache_remove_conn(connc->closure_handle, conn, TRUE);
544 Curl_disconnect(connc->closure_handle, conn, FALSE);
545 sigpipe_restore(&pipe_st);
546
547 conn = conncache_find_first_connection(connc);
548 }
549
550 connc->closure_handle->state.buffer = NULL;
551 sigpipe_ignore(connc->closure_handle, &pipe_st);
552
553 Curl_hostcache_clean(connc->closure_handle,
554 connc->closure_handle->dns.hostcache);
555 Curl_close(&connc->closure_handle);
556 sigpipe_restore(&pipe_st);
557}
558
559#if 0
560/* Useful for debugging the connection cache */
561void Curl_conncache_print(struct conncache *connc)
562{
563 struct Curl_hash_iterator iter;
564 struct Curl_llist_element *curr;
565 struct Curl_hash_element *he;
566
567 if(!connc)
568 return;
569
570 fprintf(stderr, "=Bundle cache=\n");
571
572 Curl_hash_start_iterate(connc->hash, &iter);
573
574 he = Curl_hash_next_element(&iter);
575 while(he) {
576 struct connectbundle *bundle;
577 struct connectdata *conn;
578
579 bundle = he->ptr;
580
581 fprintf(stderr, "%s -", he->key);
582 curr = bundle->conn_list->head;
583 while(curr) {
584 conn = curr->ptr;
585
586 fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
587 curr = curr->next;
588 }
589 fprintf(stderr, "\n");
590
591 he = Curl_hash_next_element(&iter);
592 }
593}
594#endif
595