Coverage Report

Created: 2023-06-07 07:02

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