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