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