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