1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 2012, 2016, Linus Nielsen Feltzing, <linus@haxx.se>
9 * Copyright (C) 2012 - 2015, 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.haxx.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 /* The last 3 #include files should be in this order */
35 #include "curl_printf.h"
36 #include "curl_memory.h"
37 #include "memdebug.h"
38
conn_llist_dtor(void * user,void * element)39 static void conn_llist_dtor(void *user, void *element)
40 {
41 struct connectdata *data = element;
42 (void)user;
43
44 data->bundle = NULL;
45 }
46
bundle_create(struct Curl_easy * data,struct connectbundle ** cb_ptr)47 static CURLcode bundle_create(struct Curl_easy *data,
48 struct connectbundle **cb_ptr)
49 {
50 (void)data;
51 DEBUGASSERT(*cb_ptr == NULL);
52 *cb_ptr = malloc(sizeof(struct connectbundle));
53 if(!*cb_ptr)
54 return CURLE_OUT_OF_MEMORY;
55
56 (*cb_ptr)->num_connections = 0;
57 (*cb_ptr)->multiuse = BUNDLE_UNKNOWN;
58
59 (*cb_ptr)->conn_list = Curl_llist_alloc((curl_llist_dtor) conn_llist_dtor);
60 if(!(*cb_ptr)->conn_list) {
61 Curl_safefree(*cb_ptr);
62 return CURLE_OUT_OF_MEMORY;
63 }
64 return CURLE_OK;
65 }
66
bundle_destroy(struct connectbundle * cb_ptr)67 static void bundle_destroy(struct connectbundle *cb_ptr)
68 {
69 if(!cb_ptr)
70 return;
71
72 if(cb_ptr->conn_list) {
73 Curl_llist_destroy(cb_ptr->conn_list, NULL);
74 cb_ptr->conn_list = NULL;
75 }
76 free(cb_ptr);
77 }
78
79 /* Add a connection to a bundle */
bundle_add_conn(struct connectbundle * cb_ptr,struct connectdata * conn)80 static CURLcode bundle_add_conn(struct connectbundle *cb_ptr,
81 struct connectdata *conn)
82 {
83 if(!Curl_llist_insert_next(cb_ptr->conn_list, cb_ptr->conn_list->tail, conn))
84 return CURLE_OUT_OF_MEMORY;
85
86 conn->bundle = cb_ptr;
87
88 cb_ptr->num_connections++;
89 return CURLE_OK;
90 }
91
92 /* Remove a connection from a bundle */
bundle_remove_conn(struct connectbundle * cb_ptr,struct connectdata * conn)93 static int bundle_remove_conn(struct connectbundle *cb_ptr,
94 struct connectdata *conn)
95 {
96 struct curl_llist_element *curr;
97
98 curr = cb_ptr->conn_list->head;
99 while(curr) {
100 if(curr->ptr == conn) {
101 Curl_llist_remove(cb_ptr->conn_list, curr, NULL);
102 cb_ptr->num_connections--;
103 conn->bundle = NULL;
104 return 1; /* we removed a handle */
105 }
106 curr = curr->next;
107 }
108 return 0;
109 }
110
free_bundle_hash_entry(void * freethis)111 static void free_bundle_hash_entry(void *freethis)
112 {
113 struct connectbundle *b = (struct connectbundle *) freethis;
114
115 bundle_destroy(b);
116 }
117
Curl_conncache_init(struct conncache * connc,int size)118 int Curl_conncache_init(struct conncache *connc, int size)
119 {
120 return Curl_hash_init(&connc->hash, size, Curl_hash_str,
121 Curl_str_key_compare, free_bundle_hash_entry);
122 }
123
Curl_conncache_destroy(struct conncache * connc)124 void Curl_conncache_destroy(struct conncache *connc)
125 {
126 if(connc)
127 Curl_hash_destroy(&connc->hash);
128 }
129
130 /* returns an allocated key to find a bundle for this connection */
hashkey(struct connectdata * conn)131 static char *hashkey(struct connectdata *conn)
132 {
133 const char *hostname;
134
135 if(conn->bits.socksproxy)
136 hostname = conn->socks_proxy.host.name;
137 else if(conn->bits.httpproxy)
138 hostname = conn->http_proxy.host.name;
139 else if(conn->bits.conn_to_host)
140 hostname = conn->conn_to_host.name;
141 else
142 hostname = conn->host.name;
143
144 return aprintf("%s:%d", hostname, conn->port);
145 }
146
147 /* Look up the bundle with all the connections to the same host this
148 connectdata struct is setup to use. */
Curl_conncache_find_bundle(struct connectdata * conn,struct conncache * connc)149 struct connectbundle *Curl_conncache_find_bundle(struct connectdata *conn,
150 struct conncache *connc)
151 {
152 struct connectbundle *bundle = NULL;
153 if(connc) {
154 char *key = hashkey(conn);
155 if(key) {
156 bundle = Curl_hash_pick(&connc->hash, key, strlen(key));
157 free(key);
158 }
159 }
160
161 return bundle;
162 }
163
conncache_add_bundle(struct conncache * connc,char * key,struct connectbundle * bundle)164 static bool conncache_add_bundle(struct conncache *connc,
165 char *key,
166 struct connectbundle *bundle)
167 {
168 void *p = Curl_hash_add(&connc->hash, key, strlen(key), bundle);
169
170 return p?TRUE:FALSE;
171 }
172
conncache_remove_bundle(struct conncache * connc,struct connectbundle * bundle)173 static void conncache_remove_bundle(struct conncache *connc,
174 struct connectbundle *bundle)
175 {
176 struct curl_hash_iterator iter;
177 struct curl_hash_element *he;
178
179 if(!connc)
180 return;
181
182 Curl_hash_start_iterate(&connc->hash, &iter);
183
184 he = Curl_hash_next_element(&iter);
185 while(he) {
186 if(he->ptr == bundle) {
187 /* The bundle is destroyed by the hash destructor function,
188 free_bundle_hash_entry() */
189 Curl_hash_delete(&connc->hash, he->key, he->key_len);
190 return;
191 }
192
193 he = Curl_hash_next_element(&iter);
194 }
195 }
196
Curl_conncache_add_conn(struct conncache * connc,struct connectdata * conn)197 CURLcode Curl_conncache_add_conn(struct conncache *connc,
198 struct connectdata *conn)
199 {
200 CURLcode result;
201 struct connectbundle *bundle;
202 struct connectbundle *new_bundle = NULL;
203 struct Curl_easy *data = conn->data;
204
205 bundle = Curl_conncache_find_bundle(conn, data->state.conn_cache);
206 if(!bundle) {
207 char *key;
208 int rc;
209
210 result = bundle_create(data, &new_bundle);
211 if(result)
212 return result;
213
214 key = hashkey(conn);
215 if(!key) {
216 bundle_destroy(new_bundle);
217 return CURLE_OUT_OF_MEMORY;
218 }
219
220 rc = conncache_add_bundle(data->state.conn_cache, key, new_bundle);
221 free(key);
222 if(!rc) {
223 bundle_destroy(new_bundle);
224 return CURLE_OUT_OF_MEMORY;
225 }
226 bundle = new_bundle;
227 }
228
229 result = bundle_add_conn(bundle, conn);
230 if(result) {
231 if(new_bundle)
232 conncache_remove_bundle(data->state.conn_cache, new_bundle);
233 return result;
234 }
235
236 conn->connection_id = connc->next_connection_id++;
237 connc->num_connections++;
238
239 DEBUGF(infof(conn->data, "Added connection %ld. "
240 "The cache now contains %" CURL_FORMAT_CURL_OFF_TU " members\n",
241 conn->connection_id, (curl_off_t) connc->num_connections));
242
243 return CURLE_OK;
244 }
245
Curl_conncache_remove_conn(struct conncache * connc,struct connectdata * conn)246 void Curl_conncache_remove_conn(struct conncache *connc,
247 struct connectdata *conn)
248 {
249 struct connectbundle *bundle = conn->bundle;
250
251 /* The bundle pointer can be NULL, since this function can be called
252 due to a failed connection attempt, before being added to a bundle */
253 if(bundle) {
254 bundle_remove_conn(bundle, conn);
255 if(bundle->num_connections == 0) {
256 conncache_remove_bundle(connc, bundle);
257 }
258
259 if(connc) {
260 connc->num_connections--;
261
262 DEBUGF(infof(conn->data, "The cache now contains %"
263 CURL_FORMAT_CURL_OFF_TU " members\n",
264 (curl_off_t) connc->num_connections));
265 }
266 }
267 }
268
269 /* This function iterates the entire connection cache and calls the
270 function func() with the connection pointer as the first argument
271 and the supplied 'param' argument as the other,
272
273 Return 0 from func() to continue the loop, return 1 to abort it.
274 */
Curl_conncache_foreach(struct conncache * connc,void * param,int (* func)(struct connectdata * conn,void * param))275 void Curl_conncache_foreach(struct conncache *connc,
276 void *param,
277 int (*func)(struct connectdata *conn, void *param))
278 {
279 struct curl_hash_iterator iter;
280 struct curl_llist_element *curr;
281 struct curl_hash_element *he;
282
283 if(!connc)
284 return;
285
286 Curl_hash_start_iterate(&connc->hash, &iter);
287
288 he = Curl_hash_next_element(&iter);
289 while(he) {
290 struct connectbundle *bundle;
291
292 bundle = he->ptr;
293 he = Curl_hash_next_element(&iter);
294
295 curr = bundle->conn_list->head;
296 while(curr) {
297 /* Yes, we need to update curr before calling func(), because func()
298 might decide to remove the connection */
299 struct connectdata *conn = curr->ptr;
300 curr = curr->next;
301
302 if(1 == func(conn, param))
303 return;
304 }
305 }
306 }
307
308 /* Return the first connection found in the cache. Used when closing all
309 connections */
310 struct connectdata *
Curl_conncache_find_first_connection(struct conncache * connc)311 Curl_conncache_find_first_connection(struct conncache *connc)
312 {
313 struct curl_hash_iterator iter;
314 struct curl_hash_element *he;
315 struct connectbundle *bundle;
316
317 Curl_hash_start_iterate(&connc->hash, &iter);
318
319 he = Curl_hash_next_element(&iter);
320 while(he) {
321 struct curl_llist_element *curr;
322 bundle = he->ptr;
323
324 curr = bundle->conn_list->head;
325 if(curr) {
326 return curr->ptr;
327 }
328
329 he = Curl_hash_next_element(&iter);
330 }
331
332 return NULL;
333 }
334
335
336 #if 0
337 /* Useful for debugging the connection cache */
338 void Curl_conncache_print(struct conncache *connc)
339 {
340 struct curl_hash_iterator iter;
341 struct curl_llist_element *curr;
342 struct curl_hash_element *he;
343
344 if(!connc)
345 return;
346
347 fprintf(stderr, "=Bundle cache=\n");
348
349 Curl_hash_start_iterate(connc->hash, &iter);
350
351 he = Curl_hash_next_element(&iter);
352 while(he) {
353 struct connectbundle *bundle;
354 struct connectdata *conn;
355
356 bundle = he->ptr;
357
358 fprintf(stderr, "%s -", he->key);
359 curr = bundle->conn_list->head;
360 while(curr) {
361 conn = curr->ptr;
362
363 fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
364 curr = curr->next;
365 }
366 fprintf(stderr, "\n");
367
368 he = Curl_hash_next_element(&iter);
369 }
370 }
371 #endif
372