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