• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
bundle_create(struct connectbundle ** bundlep)48 static CURLcode bundle_create(struct connectbundle **bundlep)
49 {
50   DEBUGASSERT(*bundlep == NULL);
51   *bundlep = malloc(sizeof(struct connectbundle));
52   if(!*bundlep)
53     return CURLE_OUT_OF_MEMORY;
54 
55   (*bundlep)->num_connections = 0;
56   (*bundlep)->multiuse = BUNDLE_UNKNOWN;
57 
58   Curl_llist_init(&(*bundlep)->conn_list, NULL);
59   return CURLE_OK;
60 }
61 
bundle_destroy(struct connectbundle * bundle)62 static void bundle_destroy(struct connectbundle *bundle)
63 {
64   free(bundle);
65 }
66 
67 /* Add a connection to a bundle */
bundle_add_conn(struct connectbundle * bundle,struct connectdata * conn)68 static void bundle_add_conn(struct connectbundle *bundle,
69                             struct connectdata *conn)
70 {
71   Curl_llist_insert_next(&bundle->conn_list, bundle->conn_list.tail, conn,
72                          &conn->bundle_node);
73   conn->bundle = bundle;
74   bundle->num_connections++;
75 }
76 
77 /* Remove a connection from a bundle */
bundle_remove_conn(struct connectbundle * bundle,struct connectdata * conn)78 static int bundle_remove_conn(struct connectbundle *bundle,
79                               struct connectdata *conn)
80 {
81   struct Curl_llist_element *curr;
82 
83   curr = bundle->conn_list.head;
84   while(curr) {
85     if(curr->ptr == conn) {
86       Curl_llist_remove(&bundle->conn_list, curr, NULL);
87       bundle->num_connections--;
88       conn->bundle = NULL;
89       return 1; /* we removed a handle */
90     }
91     curr = curr->next;
92   }
93   DEBUGASSERT(0);
94   return 0;
95 }
96 
free_bundle_hash_entry(void * freethis)97 static void free_bundle_hash_entry(void *freethis)
98 {
99   struct connectbundle *b = (struct connectbundle *) freethis;
100 
101   bundle_destroy(b);
102 }
103 
Curl_conncache_init(struct conncache * connc,int size)104 int Curl_conncache_init(struct conncache *connc, int size)
105 {
106   /* allocate a new easy handle to use when closing cached connections */
107   connc->closure_handle = curl_easy_init();
108   if(!connc->closure_handle)
109     return 1; /* bad */
110 
111   Curl_hash_init(&connc->hash, size, Curl_hash_str,
112                  Curl_str_key_compare, free_bundle_hash_entry);
113   connc->closure_handle->state.conn_cache = connc;
114 
115   return 0; /* good */
116 }
117 
Curl_conncache_destroy(struct conncache * connc)118 void Curl_conncache_destroy(struct conncache *connc)
119 {
120   if(connc)
121     Curl_hash_destroy(&connc->hash);
122 }
123 
124 /* creates a key to find a bundle for this connection */
hashkey(struct connectdata * conn,char * buf,size_t len)125 static void hashkey(struct connectdata *conn, char *buf, size_t len)
126 {
127   const char *hostname;
128   long port = conn->remote_port;
129   DEBUGASSERT(len >= HASHKEY_SIZE);
130 #ifndef CURL_DISABLE_PROXY
131   if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
132     hostname = conn->http_proxy.host.name;
133     port = conn->port;
134   }
135   else
136 #endif
137     if(conn->bits.conn_to_host)
138       hostname = conn->conn_to_host.name;
139   else
140     hostname = conn->host.name;
141 
142   /* put the numbers first so that the hostname gets cut off if too long */
143 #ifdef ENABLE_IPV6
144   msnprintf(buf, len, "%u/%ld/%s", conn->scope_id, port, hostname);
145 #else
146   msnprintf(buf, len, "%ld/%s", port, hostname);
147 #endif
148   Curl_strntolower(buf, buf, len);
149 }
150 
151 /* Returns number of connections currently held in the connection cache.
152    Locks/unlocks the cache itself!
153 */
Curl_conncache_size(struct Curl_easy * data)154 size_t Curl_conncache_size(struct Curl_easy *data)
155 {
156   size_t num;
157   CONNCACHE_LOCK(data);
158   num = data->state.conn_cache->num_conn;
159   CONNCACHE_UNLOCK(data);
160   return num;
161 }
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 *
Curl_conncache_find_bundle(struct Curl_easy * data,struct connectdata * conn,struct conncache * connc)168 Curl_conncache_find_bundle(struct Curl_easy *data,
169                            struct connectdata *conn,
170                            struct conncache *connc)
171 {
172   struct connectbundle *bundle = NULL;
173   CONNCACHE_LOCK(data);
174   if(connc) {
175     char key[HASHKEY_SIZE];
176     hashkey(conn, key, sizeof(key));
177     bundle = Curl_hash_pick(&connc->hash, key, strlen(key));
178   }
179 
180   return bundle;
181 }
182 
conncache_add_bundle(struct conncache * connc,char * key,struct connectbundle * bundle)183 static void *conncache_add_bundle(struct conncache *connc,
184                                   char *key,
185                                   struct connectbundle *bundle)
186 {
187   return Curl_hash_add(&connc->hash, key, strlen(key), bundle);
188 }
189 
conncache_remove_bundle(struct conncache * connc,struct connectbundle * bundle)190 static void conncache_remove_bundle(struct conncache *connc,
191                                     struct connectbundle *bundle)
192 {
193   struct Curl_hash_iterator iter;
194   struct Curl_hash_element *he;
195 
196   if(!connc)
197     return;
198 
199   Curl_hash_start_iterate(&connc->hash, &iter);
200 
201   he = Curl_hash_next_element(&iter);
202   while(he) {
203     if(he->ptr == bundle) {
204       /* The bundle is destroyed by the hash destructor function,
205          free_bundle_hash_entry() */
206       Curl_hash_delete(&connc->hash, he->key, he->key_len);
207       return;
208     }
209 
210     he = Curl_hash_next_element(&iter);
211   }
212 }
213 
Curl_conncache_add_conn(struct Curl_easy * data)214 CURLcode Curl_conncache_add_conn(struct Curl_easy *data)
215 {
216   CURLcode result = CURLE_OK;
217   struct connectbundle *bundle = NULL;
218   struct connectdata *conn = data->conn;
219   struct conncache *connc = data->state.conn_cache;
220   DEBUGASSERT(conn);
221 
222   /* *find_bundle() locks the connection cache */
223   bundle = Curl_conncache_find_bundle(data, conn, data->state.conn_cache);
224   if(!bundle) {
225     char key[HASHKEY_SIZE];
226 
227     result = bundle_create(&bundle);
228     if(result) {
229       goto unlock;
230     }
231 
232     hashkey(conn, key, sizeof(key));
233 
234     if(!conncache_add_bundle(data->state.conn_cache, key, bundle)) {
235       bundle_destroy(bundle);
236       result = CURLE_OUT_OF_MEMORY;
237       goto unlock;
238     }
239   }
240 
241   bundle_add_conn(bundle, conn);
242   conn->connection_id = connc->next_connection_id++;
243   connc->num_conn++;
244 
245   DEBUGF(infof(data, "Added connection %ld. "
246                "The cache now contains %zu members",
247                conn->connection_id, connc->num_conn));
248 
249   unlock:
250   CONNCACHE_UNLOCK(data);
251 
252   return result;
253 }
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  */
Curl_conncache_remove_conn(struct Curl_easy * data,struct connectdata * conn,bool lock)262 void Curl_conncache_remove_conn(struct Curl_easy *data,
263                                 struct connectdata *conn, bool lock)
264 {
265   struct connectbundle *bundle = conn->bundle;
266   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   if(bundle) {
271     if(lock) {
272       CONNCACHE_LOCK(data);
273     }
274     bundle_remove_conn(bundle, conn);
275     if(bundle->num_connections == 0)
276       conncache_remove_bundle(connc, bundle);
277     conn->bundle = NULL; /* removed from it */
278     if(connc) {
279       connc->num_conn--;
280       DEBUGF(infof(data, "The cache now contains %zu members",
281                    connc->num_conn));
282     }
283     if(lock) {
284       CONNCACHE_UNLOCK(data);
285     }
286   }
287 }
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  */
Curl_conncache_foreach(struct Curl_easy * data,struct conncache * connc,void * param,int (* func)(struct Curl_easy * data,struct connectdata * conn,void * param))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 {
307   struct Curl_hash_iterator iter;
308   struct Curl_llist_element *curr;
309   struct Curl_hash_element *he;
310 
311   if(!connc)
312     return FALSE;
313 
314   CONNCACHE_LOCK(data);
315   Curl_hash_start_iterate(&connc->hash, &iter);
316 
317   he = Curl_hash_next_element(&iter);
318   while(he) {
319     struct connectbundle *bundle;
320 
321     bundle = he->ptr;
322     he = Curl_hash_next_element(&iter);
323 
324     curr = bundle->conn_list.head;
325     while(curr) {
326       /* Yes, we need to update curr before calling func(), because func()
327          might decide to remove the connection */
328       struct connectdata *conn = curr->ptr;
329       curr = curr->next;
330 
331       if(1 == func(data, conn, param)) {
332         CONNCACHE_UNLOCK(data);
333         return TRUE;
334       }
335     }
336   }
337   CONNCACHE_UNLOCK(data);
338   return FALSE;
339 }
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 *
conncache_find_first_connection(struct conncache * connc)348 conncache_find_first_connection(struct conncache *connc)
349 {
350   struct Curl_hash_iterator iter;
351   struct Curl_hash_element *he;
352   struct connectbundle *bundle;
353 
354   Curl_hash_start_iterate(&connc->hash, &iter);
355 
356   he = Curl_hash_next_element(&iter);
357   while(he) {
358     struct Curl_llist_element *curr;
359     bundle = he->ptr;
360 
361     curr = bundle->conn_list.head;
362     if(curr) {
363       return curr->ptr;
364     }
365 
366     he = Curl_hash_next_element(&iter);
367   }
368 
369   return NULL;
370 }
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  */
Curl_conncache_return_conn(struct Curl_easy * data,struct connectdata * conn)378 bool Curl_conncache_return_conn(struct Curl_easy *data,
379                                 struct connectdata *conn)
380 {
381   /* data->multi->maxconnects can be negative, deal with it. */
382   size_t maxconnects =
383     (data->multi->maxconnects < 0) ? data->multi->num_easy * 4:
384     data->multi->maxconnects;
385   struct connectdata *conn_candidate = NULL;
386 
387   conn->lastused = Curl_now(); /* it was used up until now */
388   if(maxconnects > 0 &&
389      Curl_conncache_size(data) > maxconnects) {
390     infof(data, "Connection cache is full, closing the oldest one");
391 
392     conn_candidate = Curl_conncache_extract_oldest(data);
393     if(conn_candidate) {
394       /* the winner gets the honour of being disconnected */
395       Curl_disconnect(data, conn_candidate, /* dead_connection */ FALSE);
396     }
397   }
398 
399   return (conn_candidate == conn) ? FALSE : TRUE;
400 
401 }
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 *
Curl_conncache_extract_bundle(struct Curl_easy * data,struct connectbundle * bundle)413 Curl_conncache_extract_bundle(struct Curl_easy *data,
414                               struct connectbundle *bundle)
415 {
416   struct Curl_llist_element *curr;
417   timediff_t highscore = -1;
418   timediff_t score;
419   struct curltime now;
420   struct connectdata *conn_candidate = NULL;
421   struct connectdata *conn;
422 
423   (void)data;
424 
425   now = Curl_now();
426 
427   curr = bundle->conn_list.head;
428   while(curr) {
429     conn = curr->ptr;
430 
431     if(!CONN_INUSE(conn)) {
432       /* Set higher score for the age passed since the connection was used */
433       score = Curl_timediff(now, conn->lastused);
434 
435       if(score > highscore) {
436         highscore = score;
437         conn_candidate = conn;
438       }
439     }
440     curr = curr->next;
441   }
442   if(conn_candidate) {
443     /* remove it to prevent another thread from nicking it */
444     bundle_remove_conn(bundle, conn_candidate);
445     data->state.conn_cache->num_conn--;
446     DEBUGF(infof(data, "The cache now contains %zu members",
447                  data->state.conn_cache->num_conn));
448   }
449 
450   return conn_candidate;
451 }
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 *
Curl_conncache_extract_oldest(struct Curl_easy * data)460 Curl_conncache_extract_oldest(struct Curl_easy *data)
461 {
462   struct conncache *connc = data->state.conn_cache;
463   struct Curl_hash_iterator iter;
464   struct Curl_llist_element *curr;
465   struct Curl_hash_element *he;
466   timediff_t highscore =- 1;
467   timediff_t score;
468   struct curltime now;
469   struct connectdata *conn_candidate = NULL;
470   struct connectbundle *bundle;
471   struct connectbundle *bundle_candidate = NULL;
472 
473   now = Curl_now();
474 
475   CONNCACHE_LOCK(data);
476   Curl_hash_start_iterate(&connc->hash, &iter);
477 
478   he = Curl_hash_next_element(&iter);
479   while(he) {
480     struct connectdata *conn;
481 
482     bundle = he->ptr;
483 
484     curr = bundle->conn_list.head;
485     while(curr) {
486       conn = curr->ptr;
487 
488       if(!CONN_INUSE(conn) && !conn->bits.close &&
489          !conn->connect_only) {
490         /* Set higher score for the age passed since the connection was used */
491         score = Curl_timediff(now, conn->lastused);
492 
493         if(score > highscore) {
494           highscore = score;
495           conn_candidate = conn;
496           bundle_candidate = bundle;
497         }
498       }
499       curr = curr->next;
500     }
501 
502     he = Curl_hash_next_element(&iter);
503   }
504   if(conn_candidate) {
505     /* remove it to prevent another thread from nicking it */
506     bundle_remove_conn(bundle_candidate, conn_candidate);
507     connc->num_conn--;
508     DEBUGF(infof(data, "The cache now contains %zu members",
509                  connc->num_conn));
510   }
511   CONNCACHE_UNLOCK(data);
512 
513   return conn_candidate;
514 }
515 
Curl_conncache_close_all_connections(struct conncache * connc)516 void Curl_conncache_close_all_connections(struct conncache *connc)
517 {
518   struct connectdata *conn;
519   char buffer[READBUFFER_MIN + 1];
520   SIGPIPE_VARIABLE(pipe_st);
521   if(!connc->closure_handle)
522     return;
523   connc->closure_handle->state.buffer = buffer;
524   connc->closure_handle->set.buffer_size = READBUFFER_MIN;
525 
526   conn = conncache_find_first_connection(connc);
527   while(conn) {
528     sigpipe_ignore(connc->closure_handle, &pipe_st);
529     /* This will remove the connection from the cache */
530     connclose(conn, "kill all");
531     Curl_conncache_remove_conn(connc->closure_handle, conn, TRUE);
532     Curl_disconnect(connc->closure_handle, conn, FALSE);
533     sigpipe_restore(&pipe_st);
534 
535     conn = conncache_find_first_connection(connc);
536   }
537 
538   connc->closure_handle->state.buffer = NULL;
539   sigpipe_ignore(connc->closure_handle, &pipe_st);
540 
541   Curl_hostcache_clean(connc->closure_handle,
542                        connc->closure_handle->dns.hostcache);
543   Curl_close(&connc->closure_handle);
544   sigpipe_restore(&pipe_st);
545 }
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
583