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