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