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