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