Mercurial > hg > index.fcgi > gift-gnutella > gift-gnutella-0.0.11-1pba
diff src/gt_xfer.c @ 0:d39e1d0d75b6
initial add
author | paulo@hit-nxdomain.opendns.com |
---|---|
date | Sat, 20 Feb 2010 21:18:28 -0800 |
parents | |
children |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/gt_xfer.c Sat Feb 20 21:18:28 2010 -0800 1.3 @@ -0,0 +1,1183 @@ 1.4 +/* 1.5 + * $Id: gt_xfer.c,v 1.103 2005/01/05 14:08:40 mkern Exp $ 1.6 + * 1.7 + * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net) 1.8 + * 1.9 + * This program is free software; you can redistribute it and/or modify it 1.10 + * under the terms of the GNU General Public License as published by the 1.11 + * Free Software Foundation; either version 2, or (at your option) any 1.12 + * later version. 1.13 + * 1.14 + * This program is distributed in the hope that it will be useful, but 1.15 + * WITHOUT ANY WARRANTY; without even the implied warranty of 1.16 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1.17 + * General Public License for more details. 1.18 + */ 1.19 + 1.20 +#include "gt_gnutella.h" 1.21 + 1.22 +#include "gt_xfer_obj.h" 1.23 +#include "gt_xfer.h" 1.24 +#include "gt_http_client.h" 1.25 +#include "gt_http_server.h" 1.26 +#include "gt_share.h" 1.27 +#include "gt_share_file.h" 1.28 +#include "gt_packet.h" 1.29 +#include "gt_node.h" 1.30 +#include "gt_node_list.h" 1.31 +#include "gt_netorg.h" 1.32 +#include "gt_connect.h" 1.33 +#include "gt_bind.h" 1.34 +#include "gt_utils.h" 1.35 + 1.36 +#include "encoding/url.h" 1.37 + 1.38 +#include "transfer/source.h" 1.39 + 1.40 +/******************************************************************************/ 1.41 + 1.42 +/* maximum number of push connections in limbo each remote user */ 1.43 +#define PUSH_MAX_LIMBO gt_config_get_int("transfer/push_max_in_limbo=5") 1.44 + 1.45 +/******************************************************************************/ 1.46 + 1.47 +/* an alternative to carrying the push TTL around in the source url */ 1.48 +#define PUSH_MAX_TTL 12 1.49 + 1.50 +/* maximum time to keep push connections in "limbo" while awaiting giftd 1.51 + * to reissue Chunks */ 1.52 +#define PUSH_LIMBO_TIMEOUT (4 * MINUTES) 1.53 + 1.54 +/* how long to wait for a PUSH reply before timing out in order to free the 1.55 + * Chunk */ 1.56 +#define PUSH_WAIT_INTERVAL (30 * SECONDS) 1.57 + 1.58 +/* minimum interval between pushes */ 1.59 +#define PUSH_MIN_DEFER_TIME (30 * ESECONDS) 1.60 + 1.61 +/* maximum amount of time a push will be forced to wait before being sent */ 1.62 +#define PUSH_MAX_DEFER_TIME (10 * EMINUTES) 1.63 + 1.64 +/*****************************************************************************/ 1.65 + 1.66 +/* this stores information about an indirect ("pushed") download */ 1.67 +typedef struct gt_push_source 1.68 +{ 1.69 + gt_guid_t *guid; 1.70 + in_addr_t ip; 1.71 + in_addr_t src_ip; /* whether this push was to a local source */ 1.72 + List *xfers; /* xfers for this source */ 1.73 + List *connections; /* connection for this source */ 1.74 + time_t last_sent; /* time of last push sent to this source */ 1.75 + double defer_time; /* min time to wait before sending another push 1.76 + * doubles every push to PUSH_MAX_DEFER_TIME */ 1.77 +} GtPushSource; 1.78 + 1.79 +/*****************************************************************************/ 1.80 + 1.81 +/* Maps guid->{list of unique GtPushSources} */ 1.82 +static Dataset *gt_push_requests; 1.83 + 1.84 +/******************************************************************************/ 1.85 + 1.86 +static void push_source_reset_last_sent (GtPushSource *push_src); 1.87 +static void push_source_set_last_sent (gt_guid_t *guid, in_addr_t ip); 1.88 +static BOOL push_source_should_send (gt_guid_t *guid, in_addr_t ip); 1.89 + 1.90 +/*****************************************************************************/ 1.91 + 1.92 +/* 1.93 + * The source URL is stored on disk and could be outdated if the format has 1.94 + * changed. This updates it with any changes when we first read it from 1.95 + * the state file on startup. 1.96 + */ 1.97 +static void replace_url (Source *src, GtSource *gt) 1.98 +{ 1.99 + char *url; 1.100 + 1.101 + if (!(url = gt_source_serialize (gt))) 1.102 + return; 1.103 + 1.104 + /* swap urls */ 1.105 + FREE (src->url); 1.106 + src->url = url; 1.107 +} 1.108 + 1.109 +/******************************************************************************/ 1.110 + 1.111 +static FileShare *lookup_index (GtTransfer *xfer, char *request) 1.112 +{ 1.113 + FileShare *file; 1.114 + char *index_str; 1.115 + char *filename; 1.116 + char *decoded; 1.117 + uint32_t index; 1.118 + 1.119 + filename = request; 1.120 + index_str = string_sep (&filename, "/"); 1.121 + 1.122 + if (!filename || !index_str) 1.123 + return NULL; 1.124 + 1.125 + index = ATOUL (index_str); 1.126 + 1.127 + decoded = gt_url_decode (filename); 1.128 + file = gt_share_local_lookup_by_index (index, decoded); 1.129 + 1.130 + free (decoded); 1.131 + 1.132 + /* the filename may or may not be url encoded */ 1.133 + if (!file) 1.134 + file = gt_share_local_lookup_by_index (index, filename); 1.135 + 1.136 + return file; 1.137 +} 1.138 + 1.139 +static FileShare *lookup_urns (GtTransfer *xfer, char *urns) 1.140 +{ 1.141 + FileShare *file = NULL; 1.142 + char *urn; 1.143 + 1.144 + /* 1.145 + * Try to lookup all urns provided in the header, 1.146 + * until one is found. 1.147 + */ 1.148 + while (!file && !string_isempty (urns)) 1.149 + { 1.150 + urn = string_sep_set (&urns, ", "); 1.151 + file = gt_share_local_lookup_by_urn (urn); 1.152 + } 1.153 + 1.154 + return file; 1.155 +} 1.156 + 1.157 +static FileShare *lookup_uri_res (GtTransfer *xfer, char *request) 1.158 +{ 1.159 + FileShare *file = NULL; 1.160 + char *resolver = NULL; 1.161 + char *urn; 1.162 + 1.163 + resolver = string_sep (&request, "?"); 1.164 + urn = string_sep (&request, " "); 1.165 + 1.166 + if (resolver && !strcasecmp (resolver, "N2R")) 1.167 + { 1.168 + string_trim (request); 1.169 + file = lookup_urns (xfer, urn); 1.170 + } 1.171 + 1.172 + if (file && HTTP_DEBUG) 1.173 + GT->dbg (GT, "file=%s", share_get_hpath (file)); 1.174 + 1.175 + return file; 1.176 +} 1.177 + 1.178 +static Share *lookup_hpath (char *namespace, GtTransfer *xfer, char *request) 1.179 +{ 1.180 + char *hpath; 1.181 + Share *share; 1.182 + 1.183 + /* 1.184 + * Reconstruct the hpath 1.185 + */ 1.186 + if (!(hpath = stringf_dup ("/%s/%s", namespace, request))) 1.187 + return NULL; 1.188 + 1.189 + if (HTTP_DEBUG) 1.190 + GT->dbg (GT, "request by hpath: %s", hpath); 1.191 + 1.192 + share = GT->share_lookup (GT, SHARE_LOOKUP_HPATH, hpath); 1.193 + free (hpath); 1.194 + 1.195 + return share; 1.196 +} 1.197 + 1.198 +/* Take a request for a file, i.e. everything after GET in: 1.199 + * 1.200 + * "GET /get/1279/filename.mp3" 1.201 + * 1.202 + * and convert it to a localized path to a file. 1.203 + * 1.204 + * Path has been "secured" already if necessary. 1.205 + * 1.206 + * The path returned must be static. 1.207 + * TODO: this interface is a bit bizarre */ 1.208 +char *gt_localize_request (GtTransfer *xfer, char *s_path, int *authorized) 1.209 +{ 1.210 + static char open_path[PATH_MAX]; 1.211 + char *namespace; 1.212 + char *path, *path0; 1.213 + char *content_urns; 1.214 + FileShare *file; 1.215 + 1.216 + /* TODO: use authorized for Browse Host (BH) requests */ 1.217 + if (!STRCMP (s_path, "/")) 1.218 + { 1.219 + /* *authorized = TRUE; */ 1.220 + if (HTTP_DEBUG) 1.221 + GT->DBGFN (GT, "received unimplemented Browse Host request"); 1.222 + 1.223 + return NULL; 1.224 + } 1.225 + 1.226 + if (authorized) 1.227 + *authorized = FALSE; 1.228 + 1.229 + if (!(path0 = path = STRDUP (s_path))) 1.230 + return NULL; 1.231 + 1.232 + if (HTTP_DEBUG) 1.233 + GT->dbg (GT, "path=%s", path); 1.234 + 1.235 + /* get rid of leading slash */ 1.236 + string_sep (&path, "/"); 1.237 + namespace = string_sep (&path, "/"); 1.238 + 1.239 + if (!namespace || !path) 1.240 + { 1.241 + GT->DBGFN (GT, "null namespace or path: %s %s\n", namespace, path); 1.242 + free (path0); 1.243 + return NULL; 1.244 + } 1.245 + 1.246 + /* 1.247 + * If the client supplied "X-Gnutella-Content-URN: ", lookup 1.248 + * by that instead of the request. 1.249 + */ 1.250 + content_urns = dataset_lookupstr (xfer->header, "x-gnutella-content-urn"); 1.251 + 1.252 + if (content_urns) 1.253 + file = lookup_urns (xfer, content_urns); 1.254 + else if (!strcasecmp (namespace, "get")) 1.255 + file = lookup_index (xfer, path); 1.256 + else if (!strcasecmp (namespace, "uri-res")) 1.257 + file = lookup_uri_res (xfer, path); 1.258 + else 1.259 + file = lookup_hpath (namespace, xfer, path); 1.260 + 1.261 + /* 1.262 + * Set xfer->content_urn [which replies with 'X-Gnutella-Content-URN'] 1.263 + * to a comma-separated list of all URNs for this file. 1.264 + */ 1.265 + xfer->content_urns = gt_share_local_get_urns (file); 1.266 + 1.267 + if (!file) 1.268 + { 1.269 + if (HTTP_DEBUG) 1.270 + GT->DBGFN (GT, "bad request: /%s/%s", namespace, path); 1.271 + 1.272 + free (path0); 1.273 + return NULL; 1.274 + } 1.275 + 1.276 + free (path0); 1.277 + 1.278 + if (!share_complete (file)) 1.279 + return NULL; 1.280 + 1.281 + /* argh, need to return static data */ 1.282 + snprintf (open_path, sizeof (open_path) - 1, "%s", share_get_hpath (file)); 1.283 + 1.284 + /* try to fill in the hash */ 1.285 + xfer->hash = share_dsp_hash (file, "SHA1"); 1.286 + 1.287 + return open_path; 1.288 +} 1.289 + 1.290 +/******************************************************************************/ 1.291 + 1.292 +static char *index_request (char *request, size_t size, 1.293 + uint32_t index, const char *filename) 1.294 +{ 1.295 + /* 1.296 + * The filename may not have ever been set. Fail the request in 1.297 + * this case. 1.298 + */ 1.299 + if (!filename || string_isempty (filename)) 1.300 + return NULL; 1.301 + 1.302 + /* 1.303 + * Filename is encoded, we don't support sending unecoded requests 1.304 + * anymore. NOTE: filename doesnt have leading '/' here, that may change 1.305 + */ 1.306 + snprintf (request, size - 1, "/get/%u/%s", index, filename); 1.307 + return request; 1.308 +} 1.309 + 1.310 +/* 1.311 + * Setup a request string. Try request-by-hash (/uri-res/N2R?urn:sha1:..), 1.312 + * but if there are any problems, fallback to a "/get/<index>/<filename>" 1.313 + * request. 1.314 + */ 1.315 +static char *request_str (Source *source, uint32_t index, char *filename) 1.316 +{ 1.317 + static char request[RW_BUFFER]; 1.318 + char *hash = source->hash; 1.319 + GtSource *gt; 1.320 + 1.321 + gt = source->udata; 1.322 + assert (gt != NULL); 1.323 + 1.324 + /* 1.325 + * Do a uri-res request unless one has failed already or 1.326 + * if we have no filename and thus no choice but to use the hash. 1.327 + * (doesn't happen currently but will for download-mesh sources 1.328 + * that only have the hash and no filename) 1.329 + */ 1.330 + if (hash && (!gt->uri_res_failed || string_isempty (filename))) 1.331 + { 1.332 + char *str0, *str; 1.333 + 1.334 + if (!(str0 = STRDUP (hash))) 1.335 + return index_request (request, sizeof (request), index, filename); 1.336 + 1.337 + str = str0; 1.338 + string_sep (&str, ":"); 1.339 + 1.340 + /* hashes are canonically uppercase on the gnet */ 1.341 + string_upper (str); 1.342 + 1.343 + if (str) 1.344 + { 1.345 + snprintf (request, sizeof (request) - 1, 1.346 + "/uri-res/N2R?urn:sha1:%s", str); 1.347 + free (str0); 1.348 + return request; 1.349 + } 1.350 + 1.351 + free (str0); 1.352 + } 1.353 + 1.354 + return index_request (request, sizeof (request), index, filename); 1.355 +} 1.356 + 1.357 +/*****************************************************************************/ 1.358 +/* PUSH HANDLING */ 1.359 + 1.360 +/* 1.361 + * This code has to deal with some tricky race conditions involving 1.362 + * chunk timeouts and pushes. 1.363 + */ 1.364 + 1.365 +static GtPushSource *gt_push_source_new (gt_guid_t *guid, in_addr_t ip, 1.366 + in_addr_t src_ip) 1.367 +{ 1.368 + GtPushSource *src; 1.369 + 1.370 + if (!(src = MALLOC (sizeof (GtPushSource)))) 1.371 + return NULL; 1.372 + 1.373 + src->guid = gt_guid_dup (guid); 1.374 + src->ip = ip; 1.375 + src->src_ip = src_ip; 1.376 + src->xfers = NULL; 1.377 + src->connections = NULL; 1.378 + 1.379 + push_source_reset_last_sent (src); 1.380 + 1.381 + return src; 1.382 +} 1.383 + 1.384 +static void gt_push_source_free (GtPushSource *src) 1.385 +{ 1.386 + if (!src) 1.387 + return; 1.388 + 1.389 + assert (src->xfers == NULL); 1.390 + assert (src->connections == NULL); 1.391 + 1.392 + free (src->guid); 1.393 + free (src); 1.394 +} 1.395 + 1.396 +/*****************************************************************************/ 1.397 + 1.398 +/* TODO: break this into two parts, first part looks in the 1.399 + * list for matching ip. If none is found, look for 1.400 + * a firewalled (local ip) push source. */ 1.401 +static int find_ip (GtPushSource *src, in_addr_t *ip) 1.402 +{ 1.403 + /* If the source is a local IP address behind a non-local one, 1.404 + * authorize by just the client guid. Otherwise, use the IP. */ 1.405 + if (gt_is_local_ip (src->ip, src->src_ip) || src->ip == *ip) 1.406 + return 0; 1.407 + 1.408 + return -1; 1.409 +} 1.410 + 1.411 +static List *lookup_source_list (gt_guid_t *guid) 1.412 +{ 1.413 + List *src_list; 1.414 + 1.415 + if (!(src_list = dataset_lookup (gt_push_requests, guid, 16))) 1.416 + return NULL; 1.417 + 1.418 + return src_list; 1.419 +} 1.420 + 1.421 +static GtPushSource *push_source_lookup (gt_guid_t *guid, in_addr_t ip) 1.422 +{ 1.423 + List *requests; 1.424 + List *list; 1.425 + 1.426 + if (!(requests = lookup_source_list (guid))) 1.427 + return NULL; 1.428 + 1.429 + list = list_find_custom (requests, &ip, (ListForeachFunc)find_ip); 1.430 + return list_nth_data (list, 0); 1.431 +} 1.432 + 1.433 +/*****************************************************************************/ 1.434 + 1.435 +static void insert_source_list (gt_guid_t *guid, List *src_list) 1.436 +{ 1.437 + if (!gt_push_requests) 1.438 + gt_push_requests = dataset_new (DATASET_HASH); 1.439 + 1.440 + dataset_insert (>_push_requests, guid, 16, src_list, 0); 1.441 +} 1.442 + 1.443 +static void add_push_source (List *pushes, gt_guid_t *guid, in_addr_t ip, 1.444 + in_addr_t src_ip) 1.445 +{ 1.446 + GtPushSource *src; 1.447 + List *old_list; 1.448 + 1.449 + if (!(src = gt_push_source_new (guid, ip, src_ip))) 1.450 + return; 1.451 + 1.452 + if ((old_list = list_find_custom (pushes, &ip, (ListForeachFunc)find_ip))) 1.453 + { 1.454 + /* push source is already there */ 1.455 + gt_push_source_free (src); 1.456 + return; 1.457 + } 1.458 + 1.459 + pushes = list_prepend (pushes, src); 1.460 + insert_source_list (guid, pushes); 1.461 +} 1.462 + 1.463 +void gt_push_source_add (gt_guid_t *guid, in_addr_t ip, in_addr_t src_ip) 1.464 +{ 1.465 + List *pushes; 1.466 + 1.467 + pushes = lookup_source_list (guid); 1.468 + add_push_source (pushes, guid, ip, src_ip); 1.469 +} 1.470 + 1.471 +/*****************************************************************************/ 1.472 +/* Timing controls for push requests */ 1.473 + 1.474 +static void push_source_reset_last_sent (GtPushSource *push_src) 1.475 +{ 1.476 + push_src->last_sent = gt_uptime (); /* wrong */ 1.477 + push_src->defer_time = 0.0; 1.478 +} 1.479 + 1.480 +static void push_source_set_last_sent (gt_guid_t *guid, in_addr_t ip) 1.481 +{ 1.482 + GtPushSource *src; 1.483 + 1.484 + if (!(src = push_source_lookup (guid, ip))) 1.485 + return; 1.486 + 1.487 + time (&src->last_sent); 1.488 +} 1.489 + 1.490 +static BOOL push_source_should_send (gt_guid_t *guid, in_addr_t ip) 1.491 +{ 1.492 + GtPushSource *src; 1.493 + double deferred; 1.494 + time_t now; 1.495 + 1.496 + time (&now); 1.497 + 1.498 + if (!(src = push_source_lookup (guid, ip))) 1.499 + return FALSE; 1.500 + 1.501 + deferred = difftime (now, src->last_sent); 1.502 + 1.503 + /* randomize the defer time a bit in order to not send pushes for all 1.504 + * downloads at once */ 1.505 + if (deferred < src->defer_time + -10.0 + 20.0 * rand() / (RAND_MAX + 1.0)) 1.506 + return FALSE; 1.507 + 1.508 + /* 1.509 + * Double the defer time interval (the minimum time between sent 1.510 + * pushes for this source). 1.511 + */ 1.512 + src->defer_time *= 2; 1.513 + if (src->defer_time >= PUSH_MAX_DEFER_TIME) 1.514 + src->defer_time = PUSH_MAX_DEFER_TIME; 1.515 + 1.516 + if (src->defer_time == 0) 1.517 + src->defer_time = PUSH_MIN_DEFER_TIME; 1.518 + 1.519 + return TRUE; 1.520 +} 1.521 + 1.522 +/*****************************************************************************/ 1.523 + 1.524 +static void flush_inputs (TCPC *c) 1.525 +{ 1.526 + int ret; 1.527 + 1.528 + assert (c->fd >= 0); 1.529 + 1.530 + /* queued writes arent used by the HTTP system in this plugin, 1.531 + * so this should always be true */ 1.532 + ret = tcp_flush (c, TRUE); 1.533 + assert (ret == 0); 1.534 + 1.535 + input_remove_all (c->fd); 1.536 +} 1.537 + 1.538 +static void continue_download (GtPushSource *push_src, GtTransfer *xfer, 1.539 + TCPC *c) 1.540 +{ 1.541 + Chunk *chunk; 1.542 + 1.543 + chunk = gt_transfer_get_chunk (xfer); 1.544 + 1.545 + /* remove all previous inputs */ 1.546 + flush_inputs (c); 1.547 + 1.548 + /* HACK: remove the detach timeout */ 1.549 + timer_remove_zero (&xfer->detach_timer); 1.550 + 1.551 + /* connect the TCPC and GtTransfer */ 1.552 + gt_transfer_set_tcpc (xfer, c); 1.553 + 1.554 + /* update the IP and port for placing in Host: */ 1.555 + peer_addr (c->fd, &xfer->ip, &xfer->port); 1.556 + 1.557 + /* the connection and the chunk have met up */ 1.558 + gt_transfer_status (xfer, SOURCE_WAITING, "Received GIV response"); 1.559 + 1.560 + if (HTTP_DEBUG) 1.561 + GT->DBGSOCK (GT, c, "Continuing download for %s", xfer->request); 1.562 + 1.563 + input_add (c->fd, xfer, INPUT_WRITE, 1.564 + (InputCallback)gt_http_client_start, TIMEOUT_DEF); 1.565 +} 1.566 + 1.567 +static void reset_conn (int fd, input_id id, TCPC *c) 1.568 +{ 1.569 + /* 1.570 + * We should only get here if some data was sent, or if it timed out. In 1.571 + * which case we should close this connection, because it shouldn't be 1.572 + * sending anything. 1.573 + */ 1.574 + if (HTTP_DEBUG) 1.575 + { 1.576 + if (fd == -1) 1.577 + GT->DBGSOCK (GT, c, "connection timed out"); 1.578 + else 1.579 + GT->DBGSOCK (GT, c, "connection closed or sent invalid data"); 1.580 + } 1.581 + 1.582 + gt_push_source_remove_conn (c); 1.583 + tcp_close (c); 1.584 +} 1.585 + 1.586 +static void store_conn (GtPushSource *src, TCPC *c) 1.587 +{ 1.588 + flush_inputs (c); 1.589 + 1.590 + input_add (c->fd, c, INPUT_READ, 1.591 + (InputCallback)reset_conn, PUSH_LIMBO_TIMEOUT); 1.592 + 1.593 + assert (!list_find (src->connections, c)); 1.594 + src->connections = list_prepend (src->connections, c); 1.595 + 1.596 + if (HTTP_DEBUG) 1.597 + GT->DBGSOCK (GT, c, "storing connection"); 1.598 +} 1.599 + 1.600 +BOOL gt_push_source_add_conn (gt_guid_t *guid, in_addr_t ip, TCPC *c) 1.601 +{ 1.602 + GtTransfer *xfer; 1.603 + GtPushSource *push_src; 1.604 + 1.605 + if (!(push_src = push_source_lookup (guid, ip))) 1.606 + { 1.607 + if (HTTP_DEBUG) 1.608 + { 1.609 + GT->err (GT, "couldn't find push source %s:[%s]", 1.610 + gt_guid_str (guid), net_ip_str (ip)); 1.611 + } 1.612 + 1.613 + tcp_close (c); 1.614 + return FALSE; 1.615 + } 1.616 + 1.617 + /* 1.618 + * Don't allow too many connections in flight from the same remote user. 1.619 + */ 1.620 + if (list_length (push_src->connections) >= PUSH_MAX_LIMBO) 1.621 + { 1.622 + if (HTTP_DEBUG) 1.623 + { 1.624 + GT->DBGSOCK (GT, c, "too many push connections from %s, closing", 1.625 + gt_guid_str (guid)); 1.626 + } 1.627 + 1.628 + tcp_close (c); 1.629 + return FALSE; 1.630 + } 1.631 + 1.632 + /* 1.633 + * Since we now know this push source is alive, reset the push send 1.634 + * tracking time: in case the connection is lost, we'll resend the push 1.635 + * right away instead of waiting. 1.636 + */ 1.637 + push_source_reset_last_sent (push_src); 1.638 + 1.639 + /* 1.640 + * Store the connection if there are no GtTransfer requests from 1.641 + * giFT at the moment. 1.642 + */ 1.643 + if (!push_src->xfers) 1.644 + { 1.645 + store_conn (push_src, c); 1.646 + return FALSE; 1.647 + } 1.648 + 1.649 + xfer = list_nth_data (push_src->xfers, 0); 1.650 + push_src->xfers = list_remove (push_src->xfers, xfer); 1.651 + 1.652 + continue_download (push_src, xfer, c); 1.653 + return TRUE; 1.654 +} 1.655 + 1.656 +/* return TRUE if there's a connection residing on this push source */ 1.657 +static BOOL push_source_lookup_conn (gt_guid_t *guid, in_addr_t ip) 1.658 +{ 1.659 + GtPushSource *push_src; 1.660 + 1.661 + if (!(push_src = push_source_lookup (guid, ip))) 1.662 + return FALSE; 1.663 + 1.664 + if (push_src->connections) 1.665 + { 1.666 + if (HTTP_DEBUG) 1.667 + GT->DBGFN (GT, "found push connection for %s", net_ip_str (ip)); 1.668 + 1.669 + return TRUE; 1.670 + } 1.671 + 1.672 + return FALSE; 1.673 +} 1.674 + 1.675 +static void store_xfer (GtPushSource *src, GtTransfer *xfer) 1.676 +{ 1.677 + assert (!list_find (src->xfers, xfer)); 1.678 + src->xfers = list_prepend (src->xfers, xfer); 1.679 +} 1.680 + 1.681 +BOOL gt_push_source_add_xfer (gt_guid_t *guid, in_addr_t ip, 1.682 + in_addr_t src_ip, GtTransfer *xfer) 1.683 +{ 1.684 + TCPC *c; 1.685 + GtPushSource *push_src; 1.686 + 1.687 + assert (xfer != NULL); 1.688 + 1.689 + /* create the source if it doesn't exist already */ 1.690 + gt_push_source_add (guid, ip, src_ip); 1.691 + 1.692 + if (!(push_src = push_source_lookup (guid, ip))) 1.693 + { 1.694 + if (HTTP_DEBUG) 1.695 + { 1.696 + GT->err (GT, "couldn't find push source (%s:[%s]) for chunk %s", 1.697 + gt_guid_str (guid), net_ip_str (ip), xfer->request); 1.698 + } 1.699 + 1.700 + return FALSE; 1.701 + } 1.702 + 1.703 + /* 1.704 + * Store the GtTransfer if there are no connections to service it 1.705 + * at the moment. 1.706 + */ 1.707 + if (!push_src->connections) 1.708 + { 1.709 + store_xfer (push_src, xfer); 1.710 + return FALSE; 1.711 + } 1.712 + 1.713 + c = list_nth_data (push_src->connections, 0); 1.714 + push_src->connections = list_remove (push_src->connections, c); 1.715 + 1.716 + continue_download (push_src, xfer, c); 1.717 + return TRUE; 1.718 +} 1.719 + 1.720 +/*****************************************************************************/ 1.721 + 1.722 +static BOOL remove_xfer (GtPushSource *src, GtTransfer *xfer) 1.723 +{ 1.724 + src->xfers = list_remove (src->xfers, xfer); 1.725 + return FALSE; 1.726 +} 1.727 + 1.728 +static void remove_xfer_list (ds_data_t *key, ds_data_t *value, 1.729 + GtTransfer *xfer) 1.730 +{ 1.731 + List *src_list = value->data; 1.732 + 1.733 + list_foreach (src_list, (ListForeachFunc)remove_xfer, xfer); 1.734 +} 1.735 + 1.736 +/* 1.737 + * The chunk is being cancelled, so remove it from being tracked. 1.738 + * 1.739 + * After this, if the push recipient connects, we will have to wait 1.740 + * for another chunk timeout before transmitting. 1.741 + */ 1.742 +void gt_push_source_remove_xfer (GtTransfer *xfer) 1.743 +{ 1.744 + if (!xfer) 1.745 + return; 1.746 + 1.747 + dataset_foreach (gt_push_requests, DS_FOREACH(remove_xfer_list), xfer); 1.748 +} 1.749 + 1.750 +static BOOL remove_conn (GtPushSource *src, TCPC *c) 1.751 +{ 1.752 + src->connections = list_remove (src->connections, c); 1.753 + return FALSE; 1.754 +} 1.755 + 1.756 +static void remove_conn_list (ds_data_t *key, ds_data_t *value, TCPC *c) 1.757 +{ 1.758 + List *src_list = value->data; 1.759 + 1.760 + list_foreach (src_list, (ListForeachFunc)remove_conn, c); 1.761 +} 1.762 + 1.763 +/* 1.764 + * The connection from this push download closed 1.765 + */ 1.766 +void gt_push_source_remove_conn (TCPC *c) 1.767 +{ 1.768 + if (!c) 1.769 + return; 1.770 + 1.771 + dataset_foreach (gt_push_requests, DS_FOREACH(remove_conn_list), c); 1.772 +} 1.773 + 1.774 +static BOOL cleanup_xfer (GtTransfer *xfer, void *udata) 1.775 +{ 1.776 + gt_push_source_remove_xfer (xfer); 1.777 + return TRUE; 1.778 +} 1.779 + 1.780 +static BOOL cleanup_conn (TCPC *c, void *udata) 1.781 +{ 1.782 + gt_push_source_remove_conn (c); 1.783 + tcp_close (c); 1.784 + return TRUE; 1.785 +} 1.786 + 1.787 +static void remove_push_source (GtPushSource *src) 1.788 +{ 1.789 + List *src_list; 1.790 + 1.791 + src_list = lookup_source_list (src->guid); 1.792 + src_list = list_remove (src_list, src); 1.793 + 1.794 + insert_source_list (src->guid, src_list); 1.795 +} 1.796 + 1.797 +void gt_push_source_remove (gt_guid_t *guid, in_addr_t ip, in_addr_t src_ip) 1.798 +{ 1.799 + GtPushSource *src; 1.800 + 1.801 + if (!(src = push_source_lookup (guid, ip))) 1.802 + return; 1.803 + 1.804 + /* cleanup all the chunks and connections */ 1.805 + src->xfers = 1.806 + list_foreach_remove (src->xfers, (ListForeachFunc)cleanup_xfer, 1.807 + NULL); 1.808 + src->connections = 1.809 + list_foreach_remove (src->connections, (ListForeachFunc)cleanup_conn, 1.810 + NULL); 1.811 + 1.812 + /* remove this source from the global list */ 1.813 + remove_push_source (src); 1.814 + 1.815 + gt_push_source_free (src); 1.816 +} 1.817 + 1.818 +/*****************************************************************************/ 1.819 + 1.820 +static BOOL detach_timeout (void *udata) 1.821 +{ 1.822 + GtTransfer *xfer = udata; 1.823 + 1.824 + /* Added this on 2004-12-22 to track observed assertion failure in 1.825 + * gt_transfer_get_chunk. -- mkern 1.826 + */ 1.827 + if (!xfer->chunk || xfer->chunk->udata != xfer) 1.828 + { 1.829 + GT->DBGFN (GT, "Detach timeout troubles. status = %d, " 1.830 + "text = %s, xfer->ip = %s, " 1.831 + "xfer = %p, xfer->chunk->udata = %p, " 1.832 + "xfer->detach_timer = 0x%X", 1.833 + xfer->detach_status, xfer->detach_msgtxt, 1.834 + net_ip_str (xfer->ip), xfer, 1.835 + xfer->chunk->udata, xfer->detach_timer); 1.836 + } 1.837 + 1.838 + /* Sometimes gt_transfer_status will trigger an 1.839 + * assert (xfer->chunk->udata == xfer) failure in gt_transfer_get_chunk. 1.840 + * But why? Is xfer already freed? Does it have another chunk and the 1.841 + * timer was not removed? 1.842 + */ 1.843 + gt_transfer_status (xfer, xfer->detach_status, xfer->detach_msgtxt); 1.844 + gt_transfer_close (xfer, TRUE); 1.845 + 1.846 + return FALSE; 1.847 +} 1.848 + 1.849 +/* 1.850 + * Attach a timer that will "detach" the GtTransfer from the Chunk by 1.851 + * cancelling it, but pretend that the Source is in some other state besides 1.852 + * "cancelled" or "timed out" by changing the status text. 1.853 + * 1.854 + * This is useful to keep a semi-consistent UI in certain situations, such as 1.855 + * sending out push requests, and cancelling requests when the remote side has 1.856 + * queued our request. 1.857 + */ 1.858 +static void detach_transfer_in (GtTransfer *xfer, SourceStatus status, 1.859 + char *status_txt, time_t interval) 1.860 +{ 1.861 + char *msg; 1.862 + 1.863 + msg = STRDUP (status_txt); 1.864 + 1.865 + gt_transfer_status (xfer, status, msg); 1.866 + xfer->detach_status = status; 1.867 + 1.868 + free (xfer->detach_msgtxt); 1.869 + xfer->detach_msgtxt = msg; 1.870 + 1.871 + xfer->detach_timer = timer_add (interval, 1.872 + (TimerCallback)detach_timeout, xfer); 1.873 +} 1.874 + 1.875 +/* 1.876 + * Detach an GtTransfer from its Chunk. 1.877 + */ 1.878 +static void detach_transfer (GtTransfer *xfer, SourceStatus status, 1.879 + char *msgtxt) 1.880 +{ 1.881 + /* 1.882 + * Cancelling from p->download_start will cause download_pause() to crash. 1.883 + * So, the detach must happen from timer context. 1.884 + */ 1.885 + detach_transfer_in (xfer, status, msgtxt, 2 * SECONDS); 1.886 +} 1.887 + 1.888 +/*****************************************************************************/ 1.889 + 1.890 +static void send_push (GtTransfer *xfer, GtSource *gt, TCPC *server) 1.891 +{ 1.892 + GtPacket *packet; 1.893 + 1.894 + if (!(packet = gt_packet_new (GT_MSG_PUSH, PUSH_MAX_TTL, NULL))) 1.895 + return; 1.896 + 1.897 + gt_packet_put_ustr (packet, gt->guid, 16); 1.898 + gt_packet_put_uint32 (packet, gt->index); 1.899 + gt_packet_put_ip (packet, GT_NODE(server)->my_ip); 1.900 + gt_packet_put_port (packet, GT_SELF->gt_port); 1.901 + 1.902 + if (gt_packet_error (packet)) 1.903 + { 1.904 + gt_packet_free (packet); 1.905 + return; 1.906 + } 1.907 + 1.908 + gt_packet_send (server, packet); 1.909 + gt_packet_free (packet); 1.910 + 1.911 + /* 1.912 + * Don't wait for the whole Chunk timeout -- that keeps the Chunk 1.913 + * occupied for too long if there are other active sources (the Chunk 1.914 + * also times out longer and longer each time, so this gets worse 1.915 + * the longer the transfer is inactive). 1.916 + * 1.917 + * This is really an infelicity of the Chunk system. 1.918 + */ 1.919 + detach_transfer_in (xfer, SOURCE_QUEUED_REMOTE, "Sent PUSH request", 1.920 + PUSH_WAIT_INTERVAL); 1.921 + 1.922 + /* set the last time we sent a push to now */ 1.923 + push_source_set_last_sent (gt->guid, gt->user_ip); 1.924 +} 1.925 + 1.926 +static BOOL send_push_to_server (in_addr_t server_ip, in_port_t server_port, 1.927 + GtTransfer *xfer, GtSource *gt) 1.928 +{ 1.929 + GtNode *server; 1.930 + 1.931 + if (!(server = gt_node_lookup (server_ip, server_port))) 1.932 + { 1.933 + server = gt_node_register (server_ip, server_port, 1.934 + GT_NODE_ULTRA); 1.935 + } 1.936 + 1.937 + if (!server) 1.938 + { 1.939 + GT->err (GT, "couldn't register server"); 1.940 + return FALSE; 1.941 + } 1.942 + 1.943 + if (server->state & (GT_NODE_CONNECTED | GT_NODE_CONNECTING_2)) 1.944 + { 1.945 + assert (GT_CONN(server) != NULL); 1.946 + 1.947 + /* Server is in a state for receiving packets -- send the push */ 1.948 + send_push (xfer, gt, GT_CONN(server)); 1.949 + return TRUE; 1.950 + } 1.951 + else if (server->state & GT_NODE_CONNECTING_1) 1.952 + { 1.953 + /* dont try to connect again; wait till we're connected */ 1.954 + return FALSE; 1.955 + } 1.956 + else if (gt_conn_need_connections (GT_NODE_ULTRA) > 0 && 1.957 + !server->tried_connect && 1.958 + gt_connect (server) >= 0) 1.959 + { 1.960 + /* 1.961 + * We've tried to connect to the server so we could deliver the push 1.962 + * request eventually NOTE: this doesnt send a push until the next 1.963 + * chunk timeout. 1.964 + */ 1.965 + return FALSE; 1.966 + } 1.967 + 1.968 + return FALSE; 1.969 +} 1.970 + 1.971 +static void handle_push_download (Chunk *chunk, GtTransfer *xfer, GtSource *gt) 1.972 +{ 1.973 + GtNode *server; 1.974 + 1.975 + /* 1.976 + * If this succeeds, we already have a connection to this 1.977 + * user and the transfer will continue by using that connection. 1.978 + * 1.979 + * TODO: the gt_push_source_add() should be used by some 1.980 + * per-source data structure 1.981 + */ 1.982 + if (gt_push_source_add_xfer (gt->guid, gt->user_ip, gt->server_ip, xfer)) 1.983 + return; 1.984 + 1.985 + /* 1.986 + * Dont send pushes too often. Maybe should use a global queue instead. 1.987 + * 1.988 + * NOTE: we can't free the xfer here because we have stored it. 1.989 + */ 1.990 + if (push_source_should_send (gt->guid, gt->user_ip) == FALSE) 1.991 + { 1.992 + /* don't occupy the Chunk forever */ 1.993 + detach_transfer_in (xfer, SOURCE_QUEUED_REMOTE, "Awaiting connection", 1.994 + PUSH_WAIT_INTERVAL); 1.995 + return; 1.996 + } 1.997 + 1.998 + /* 1.999 + * Next, try to find the server that supplied this result, 1.1000 + * and send them a push. 1.1001 + */ 1.1002 + if (send_push_to_server (gt->server_ip, gt->server_port, xfer, gt)) 1.1003 + return; 1.1004 + 1.1005 + /* 1.1006 + * Finally, try sending to a random connected server. 1.1007 + * 1.1008 + * TODO: these should be rate-limited, either globally or 1.1009 + * per-source. 1.1010 + */ 1.1011 + if ((server = gt_conn_random (GT_NODE_ULTRA, GT_NODE_CONNECTED))) 1.1012 + { 1.1013 + send_push_to_server (server->ip, server->gt_port, xfer, gt); 1.1014 + return; 1.1015 + } 1.1016 + 1.1017 + detach_transfer (xfer, SOURCE_QUEUED_REMOTE, "No PUSH route"); 1.1018 +} 1.1019 + 1.1020 +static BOOL set_request (GtTransfer *xfer, Chunk *chunk, Source *source, 1.1021 + GtSource *gt_src) 1.1022 +{ 1.1023 + char *request; 1.1024 + 1.1025 + if (!chunk || !xfer) 1.1026 + return FALSE; 1.1027 + 1.1028 + request = request_str (source, gt_src->index, gt_src->filename); 1.1029 + 1.1030 + if (!gt_transfer_set_request (xfer, request)) 1.1031 + { 1.1032 + GT->DBGFN (GT, "UI made an invalid request for '%s'", request); 1.1033 + return FALSE; 1.1034 + } 1.1035 + 1.1036 + /* connect the xfer and the chunk */ 1.1037 + gt_transfer_set_chunk (xfer, chunk); 1.1038 + 1.1039 + return TRUE; 1.1040 +} 1.1041 + 1.1042 +static BOOL should_push (GtSource *gt) 1.1043 +{ 1.1044 + TCPC *persistent; 1.1045 + 1.1046 + /* we cannot push if there is no guid to send the push to */ 1.1047 + if (gt_guid_is_empty (gt->guid)) 1.1048 + return FALSE; 1.1049 + 1.1050 + persistent = gt_http_connection_lookup (GT_TRANSFER_DOWNLOAD, 1.1051 + gt->user_ip, 1.1052 + gt->user_port); 1.1053 + 1.1054 + /* need to close the connection to re-add it to the list, because 1.1055 + * _lookup removes it from the persistent connection list */ 1.1056 + gt_http_connection_close (GT_TRANSFER_DOWNLOAD, persistent, FALSE); 1.1057 + 1.1058 + /* if we already have a connection don't send a push */ 1.1059 + if (persistent) 1.1060 + return FALSE; 1.1061 + 1.1062 + /* now check for a persistent "pushed" connection, which would be stored 1.1063 + * separately from a directly connected one */ 1.1064 + if (push_source_lookup_conn (gt->guid, gt->user_ip)) 1.1065 + return TRUE; 1.1066 + 1.1067 + /* send a push if the IP is local */ 1.1068 + if (gt_is_local_ip (gt->user_ip, gt->server_ip)) 1.1069 + return TRUE; 1.1070 + 1.1071 + /* don't send a push if we cannot receive incoming connections */ 1.1072 + if (gt_bind_is_firewalled()) 1.1073 + return FALSE; 1.1074 + 1.1075 + /* send a push if they set the firewalled bit */ 1.1076 + if (gt->firewalled) 1.1077 + return TRUE; 1.1078 + 1.1079 + /* the last connection attempt failed, so try a push */ 1.1080 + if (gt->connect_failed) 1.1081 + return TRUE; 1.1082 + 1.1083 + return FALSE; 1.1084 +} 1.1085 + 1.1086 +static void handle_download (Chunk *chunk, GtTransfer *xfer, GtSource *gt) 1.1087 +{ 1.1088 + /* 1.1089 + * Send a push, or connect directly. 1.1090 + */ 1.1091 + if (should_push (gt)) 1.1092 + { 1.1093 + /* (possibly) retry a connection attempt next time */ 1.1094 + gt->connect_failed = FALSE; 1.1095 + 1.1096 + handle_push_download (chunk, xfer, gt); 1.1097 + } 1.1098 + else 1.1099 + { 1.1100 + gt_http_client_get (chunk, xfer); 1.1101 + } 1.1102 +} 1.1103 + 1.1104 +static BOOL download_is_queued (GtSource *gt) 1.1105 +{ 1.1106 + /* back out if the request is still too early */ 1.1107 + if (time (NULL) < gt->retry_time) 1.1108 + return TRUE; 1.1109 + 1.1110 + return FALSE; 1.1111 +} 1.1112 + 1.1113 +int gnutella_download_start (Protocol *p, Transfer *transfer, Chunk *chunk, 1.1114 + Source *source) 1.1115 +{ 1.1116 + GtTransfer *xfer; 1.1117 + GtSource *gt; 1.1118 + off_t start; 1.1119 + off_t stop; 1.1120 + 1.1121 + gt = source->udata; 1.1122 + assert (gt != NULL); 1.1123 + 1.1124 + /* giftd should send us only deactivated Chunks */ 1.1125 + assert (chunk->udata == NULL); 1.1126 + 1.1127 + /* free the Source URL and update it with any format changes */ 1.1128 + replace_url (source, gt); 1.1129 + 1.1130 + /* thank you, pretender :) */ 1.1131 + start = chunk->start + chunk->transmit; 1.1132 + stop = chunk->stop; 1.1133 + 1.1134 + if (!(xfer = gt_transfer_new (GT_TRANSFER_DOWNLOAD, source, 1.1135 + gt->user_ip, gt->user_port, start, stop))) 1.1136 + { 1.1137 + GT->DBGFN (GT, "gt_transfer_new failed"); 1.1138 + return FALSE; 1.1139 + } 1.1140 + 1.1141 + if (!set_request (xfer, chunk, source, gt)) 1.1142 + { 1.1143 + gt_transfer_close (xfer, TRUE); 1.1144 + return FALSE; 1.1145 + } 1.1146 + 1.1147 + if (download_is_queued (gt)) 1.1148 + { 1.1149 + detach_transfer (xfer, SOURCE_QUEUED_REMOTE, gt->status_txt); 1.1150 + return TRUE; 1.1151 + } 1.1152 + 1.1153 + handle_download (chunk, xfer, gt); 1.1154 + 1.1155 + return TRUE; 1.1156 +} 1.1157 + 1.1158 +void gnutella_download_stop (Protocol *p, Transfer *transfer, Chunk *chunk, 1.1159 + Source *source, BOOL complete) 1.1160 +{ 1.1161 + gt_download_cancel (chunk, NULL); 1.1162 +} 1.1163 + 1.1164 +int gnutella_upload_start (Protocol *p, Transfer *transfer, Chunk *chunk, 1.1165 + Source *source, unsigned long avail) 1.1166 +{ 1.1167 + return TRUE; 1.1168 +} 1.1169 + 1.1170 +void gnutella_upload_stop (Protocol *p, Transfer *transfer, Chunk *chunk, 1.1171 + Source *source) 1.1172 +{ 1.1173 + gt_upload_cancel (chunk, NULL); 1.1174 +} 1.1175 + 1.1176 +int gnutella_chunk_suspend (Protocol *p, Transfer *transfer, Chunk *chunk, 1.1177 + Source *source) 1.1178 +{ 1.1179 + return gt_chunk_suspend (chunk, transfer, NULL); 1.1180 +} 1.1181 + 1.1182 +int gnutella_chunk_resume (Protocol *p, Transfer *transfer, Chunk *chunk, 1.1183 + Source *source) 1.1184 +{ 1.1185 + return gt_chunk_resume (chunk, transfer, NULL); 1.1186 +}