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