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