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_append(&bundle->conn_list, conn, &conn->bundle_node);
72 conn->bundle = bundle;
73 bundle->num_connections++;
74 }
75
76 /* Remove a connection from a bundle */
bundle_remove_conn(struct connectbundle * bundle,struct connectdata * conn)77 static int bundle_remove_conn(struct connectbundle *bundle,
78 struct connectdata *conn)
79 {
80 struct Curl_llist_element *curr;
81
82 curr = bundle->conn_list.head;
83 while(curr) {
84 if(curr->ptr == conn) {
85 Curl_llist_remove(&bundle->conn_list, curr, NULL);
86 bundle->num_connections--;
87 conn->bundle = NULL;
88 return 1; /* we removed a handle */
89 }
90 curr = curr->next;
91 }
92 DEBUGASSERT(0);
93 return 0;
94 }
95
free_bundle_hash_entry(void * freethis)96 static void free_bundle_hash_entry(void *freethis)
97 {
98 struct connectbundle *b = (struct connectbundle *) freethis;
99
100 bundle_destroy(b);
101 }
102
Curl_conncache_init(struct conncache * connc,size_t size)103 int Curl_conncache_init(struct conncache *connc, size_t size)
104 {
105 /* allocate a new easy handle to use when closing cached connections */
106 connc->closure_handle = curl_easy_init();
107 if(!connc->closure_handle)
108 return 1; /* bad */
109 connc->closure_handle->state.internal = true;
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->primary.remote_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 USE_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 %" CURL_FORMAT_CURL_OFF_T ". "
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 unsigned int maxconnects = !data->multi->maxconnects ?
382 data->multi->num_easy * 4: data->multi->maxconnects;
383 struct connectdata *conn_candidate = NULL;
384
385 conn->lastused = Curl_now(); /* it was used up until now */
386 if(maxconnects && Curl_conncache_size(data) > maxconnects) {
387 infof(data, "Connection cache is full, closing the oldest one");
388
389 conn_candidate = Curl_conncache_extract_oldest(data);
390 if(conn_candidate) {
391 /* Use the closure handle for this disconnect so that anything that
392 happens during the disconnect is not stored and associated with the
393 'data' handle which already just finished a transfer and it is
394 important that details from this (unrelated) disconnect does not
395 taint meta-data in the data handle. */
396 struct conncache *connc = data->state.conn_cache;
397 Curl_disconnect(connc->closure_handle, conn_candidate,
398 /* dead_connection */ FALSE);
399 }
400 }
401
402 return (conn_candidate == conn) ? FALSE : TRUE;
403
404 }
405
406 /*
407 * This function finds the connection in the connection bundle that has been
408 * unused for the longest time.
409 *
410 * Does not lock the connection cache!
411 *
412 * Returns the pointer to the oldest idle connection, or NULL if none was
413 * found.
414 */
415 struct connectdata *
Curl_conncache_extract_bundle(struct Curl_easy * data,struct connectbundle * bundle)416 Curl_conncache_extract_bundle(struct Curl_easy *data,
417 struct connectbundle *bundle)
418 {
419 struct Curl_llist_element *curr;
420 timediff_t highscore = -1;
421 timediff_t score;
422 struct curltime now;
423 struct connectdata *conn_candidate = NULL;
424 struct connectdata *conn;
425
426 (void)data;
427
428 now = Curl_now();
429
430 curr = bundle->conn_list.head;
431 while(curr) {
432 conn = curr->ptr;
433
434 if(!CONN_INUSE(conn)) {
435 /* Set higher score for the age passed since the connection was used */
436 score = Curl_timediff(now, conn->lastused);
437
438 if(score > highscore) {
439 highscore = score;
440 conn_candidate = conn;
441 }
442 }
443 curr = curr->next;
444 }
445 if(conn_candidate) {
446 /* remove it to prevent another thread from nicking it */
447 bundle_remove_conn(bundle, conn_candidate);
448 data->state.conn_cache->num_conn--;
449 DEBUGF(infof(data, "The cache now contains %zu members",
450 data->state.conn_cache->num_conn));
451 }
452
453 return conn_candidate;
454 }
455
456 /*
457 * This function finds the connection in the connection cache that has been
458 * unused for the longest time and extracts that from the bundle.
459 *
460 * Returns the pointer to the connection, or NULL if none was found.
461 */
462 struct connectdata *
Curl_conncache_extract_oldest(struct Curl_easy * data)463 Curl_conncache_extract_oldest(struct Curl_easy *data)
464 {
465 struct conncache *connc = data->state.conn_cache;
466 struct Curl_hash_iterator iter;
467 struct Curl_llist_element *curr;
468 struct Curl_hash_element *he;
469 timediff_t highscore =- 1;
470 timediff_t score;
471 struct curltime now;
472 struct connectdata *conn_candidate = NULL;
473 struct connectbundle *bundle;
474 struct connectbundle *bundle_candidate = NULL;
475
476 now = Curl_now();
477
478 CONNCACHE_LOCK(data);
479 Curl_hash_start_iterate(&connc->hash, &iter);
480
481 he = Curl_hash_next_element(&iter);
482 while(he) {
483 struct connectdata *conn;
484
485 bundle = he->ptr;
486
487 curr = bundle->conn_list.head;
488 while(curr) {
489 conn = curr->ptr;
490
491 if(!CONN_INUSE(conn) && !conn->bits.close &&
492 !conn->connect_only) {
493 /* Set higher score for the age passed since the connection was used */
494 score = Curl_timediff(now, conn->lastused);
495
496 if(score > highscore) {
497 highscore = score;
498 conn_candidate = conn;
499 bundle_candidate = bundle;
500 }
501 }
502 curr = curr->next;
503 }
504
505 he = Curl_hash_next_element(&iter);
506 }
507 if(conn_candidate) {
508 /* remove it to prevent another thread from nicking it */
509 bundle_remove_conn(bundle_candidate, conn_candidate);
510 connc->num_conn--;
511 DEBUGF(infof(data, "The cache now contains %zu members",
512 connc->num_conn));
513 }
514 CONNCACHE_UNLOCK(data);
515
516 return conn_candidate;
517 }
518
Curl_conncache_close_all_connections(struct conncache * connc)519 void Curl_conncache_close_all_connections(struct conncache *connc)
520 {
521 struct connectdata *conn;
522 SIGPIPE_VARIABLE(pipe_st);
523 if(!connc->closure_handle)
524 return;
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 sigpipe_ignore(connc->closure_handle, &pipe_st);
539
540 Curl_hostcache_clean(connc->closure_handle,
541 connc->closure_handle->dns.hostcache);
542 Curl_close(&connc->closure_handle);
543 sigpipe_restore(&pipe_st);
544 }
545
546 #if 0
547 /* Useful for debugging the connection cache */
548 void Curl_conncache_print(struct conncache *connc)
549 {
550 struct Curl_hash_iterator iter;
551 struct Curl_llist_element *curr;
552 struct Curl_hash_element *he;
553
554 if(!connc)
555 return;
556
557 fprintf(stderr, "=Bundle cache=\n");
558
559 Curl_hash_start_iterate(connc->hash, &iter);
560
561 he = Curl_hash_next_element(&iter);
562 while(he) {
563 struct connectbundle *bundle;
564 struct connectdata *conn;
565
566 bundle = he->ptr;
567
568 fprintf(stderr, "%s -", he->key);
569 curr = bundle->conn_list->head;
570 while(curr) {
571 conn = curr->ptr;
572
573 fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
574 curr = curr->next;
575 }
576 fprintf(stderr, "\n");
577
578 he = Curl_hash_next_element(&iter);
579 }
580 }
581 #endif
582