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