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