Mercurial > hg > index.fcgi > gift-gnutella > gift-gnutella-0.0.11-1pba
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:a3c0967b799a |
---|---|
1 /* | |
2 * $Id: gt_xfer.c,v 1.103 2005/01/05 14:08:40 mkern Exp $ | |
3 * | |
4 * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net) | |
5 * | |
6 * This program is free software; you can redistribute it and/or modify it | |
7 * under the terms of the GNU General Public License as published by the | |
8 * Free Software Foundation; either version 2, or (at your option) any | |
9 * later version. | |
10 * | |
11 * This program is distributed in the hope that it will be useful, but | |
12 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 * General Public License for more details. | |
15 */ | |
16 | |
17 #include "gt_gnutella.h" | |
18 | |
19 #include "gt_xfer_obj.h" | |
20 #include "gt_xfer.h" | |
21 #include "gt_http_client.h" | |
22 #include "gt_http_server.h" | |
23 #include "gt_share.h" | |
24 #include "gt_share_file.h" | |
25 #include "gt_packet.h" | |
26 #include "gt_node.h" | |
27 #include "gt_node_list.h" | |
28 #include "gt_netorg.h" | |
29 #include "gt_connect.h" | |
30 #include "gt_bind.h" | |
31 #include "gt_utils.h" | |
32 | |
33 #include "encoding/url.h" | |
34 | |
35 #include "transfer/source.h" | |
36 | |
37 /******************************************************************************/ | |
38 | |
39 /* maximum number of push connections in limbo each remote user */ | |
40 #define PUSH_MAX_LIMBO gt_config_get_int("transfer/push_max_in_limbo=5") | |
41 | |
42 /******************************************************************************/ | |
43 | |
44 /* an alternative to carrying the push TTL around in the source url */ | |
45 #define PUSH_MAX_TTL 12 | |
46 | |
47 /* maximum time to keep push connections in "limbo" while awaiting giftd | |
48 * to reissue Chunks */ | |
49 #define PUSH_LIMBO_TIMEOUT (4 * MINUTES) | |
50 | |
51 /* how long to wait for a PUSH reply before timing out in order to free the | |
52 * Chunk */ | |
53 #define PUSH_WAIT_INTERVAL (30 * SECONDS) | |
54 | |
55 /* minimum interval between pushes */ | |
56 #define PUSH_MIN_DEFER_TIME (30 * ESECONDS) | |
57 | |
58 /* maximum amount of time a push will be forced to wait before being sent */ | |
59 #define PUSH_MAX_DEFER_TIME (10 * EMINUTES) | |
60 | |
61 /*****************************************************************************/ | |
62 | |
63 /* this stores information about an indirect ("pushed") download */ | |
64 typedef struct gt_push_source | |
65 { | |
66 gt_guid_t *guid; | |
67 in_addr_t ip; | |
68 in_addr_t src_ip; /* whether this push was to a local source */ | |
69 List *xfers; /* xfers for this source */ | |
70 List *connections; /* connection for this source */ | |
71 time_t last_sent; /* time of last push sent to this source */ | |
72 double defer_time; /* min time to wait before sending another push | |
73 * doubles every push to PUSH_MAX_DEFER_TIME */ | |
74 } GtPushSource; | |
75 | |
76 /*****************************************************************************/ | |
77 | |
78 /* Maps guid->{list of unique GtPushSources} */ | |
79 static Dataset *gt_push_requests; | |
80 | |
81 /******************************************************************************/ | |
82 | |
83 static void push_source_reset_last_sent (GtPushSource *push_src); | |
84 static void push_source_set_last_sent (gt_guid_t *guid, in_addr_t ip); | |
85 static BOOL push_source_should_send (gt_guid_t *guid, in_addr_t ip); | |
86 | |
87 /*****************************************************************************/ | |
88 | |
89 /* | |
90 * The source URL is stored on disk and could be outdated if the format has | |
91 * changed. This updates it with any changes when we first read it from | |
92 * the state file on startup. | |
93 */ | |
94 static void replace_url (Source *src, GtSource *gt) | |
95 { | |
96 char *url; | |
97 | |
98 if (!(url = gt_source_serialize (gt))) | |
99 return; | |
100 | |
101 /* swap urls */ | |
102 FREE (src->url); | |
103 src->url = url; | |
104 } | |
105 | |
106 /******************************************************************************/ | |
107 | |
108 static FileShare *lookup_index (GtTransfer *xfer, char *request) | |
109 { | |
110 FileShare *file; | |
111 char *index_str; | |
112 char *filename; | |
113 char *decoded; | |
114 uint32_t index; | |
115 | |
116 filename = request; | |
117 index_str = string_sep (&filename, "/"); | |
118 | |
119 if (!filename || !index_str) | |
120 return NULL; | |
121 | |
122 index = ATOUL (index_str); | |
123 | |
124 decoded = gt_url_decode (filename); | |
125 file = gt_share_local_lookup_by_index (index, decoded); | |
126 | |
127 free (decoded); | |
128 | |
129 /* the filename may or may not be url encoded */ | |
130 if (!file) | |
131 file = gt_share_local_lookup_by_index (index, filename); | |
132 | |
133 return file; | |
134 } | |
135 | |
136 static FileShare *lookup_urns (GtTransfer *xfer, char *urns) | |
137 { | |
138 FileShare *file = NULL; | |
139 char *urn; | |
140 | |
141 /* | |
142 * Try to lookup all urns provided in the header, | |
143 * until one is found. | |
144 */ | |
145 while (!file && !string_isempty (urns)) | |
146 { | |
147 urn = string_sep_set (&urns, ", "); | |
148 file = gt_share_local_lookup_by_urn (urn); | |
149 } | |
150 | |
151 return file; | |
152 } | |
153 | |
154 static FileShare *lookup_uri_res (GtTransfer *xfer, char *request) | |
155 { | |
156 FileShare *file = NULL; | |
157 char *resolver = NULL; | |
158 char *urn; | |
159 | |
160 resolver = string_sep (&request, "?"); | |
161 urn = string_sep (&request, " "); | |
162 | |
163 if (resolver && !strcasecmp (resolver, "N2R")) | |
164 { | |
165 string_trim (request); | |
166 file = lookup_urns (xfer, urn); | |
167 } | |
168 | |
169 if (file && HTTP_DEBUG) | |
170 GT->dbg (GT, "file=%s", share_get_hpath (file)); | |
171 | |
172 return file; | |
173 } | |
174 | |
175 static Share *lookup_hpath (char *namespace, GtTransfer *xfer, char *request) | |
176 { | |
177 char *hpath; | |
178 Share *share; | |
179 | |
180 /* | |
181 * Reconstruct the hpath | |
182 */ | |
183 if (!(hpath = stringf_dup ("/%s/%s", namespace, request))) | |
184 return NULL; | |
185 | |
186 if (HTTP_DEBUG) | |
187 GT->dbg (GT, "request by hpath: %s", hpath); | |
188 | |
189 share = GT->share_lookup (GT, SHARE_LOOKUP_HPATH, hpath); | |
190 free (hpath); | |
191 | |
192 return share; | |
193 } | |
194 | |
195 /* Take a request for a file, i.e. everything after GET in: | |
196 * | |
197 * "GET /get/1279/filename.mp3" | |
198 * | |
199 * and convert it to a localized path to a file. | |
200 * | |
201 * Path has been "secured" already if necessary. | |
202 * | |
203 * The path returned must be static. | |
204 * TODO: this interface is a bit bizarre */ | |
205 char *gt_localize_request (GtTransfer *xfer, char *s_path, int *authorized) | |
206 { | |
207 static char open_path[PATH_MAX]; | |
208 char *namespace; | |
209 char *path, *path0; | |
210 char *content_urns; | |
211 FileShare *file; | |
212 | |
213 /* TODO: use authorized for Browse Host (BH) requests */ | |
214 if (!STRCMP (s_path, "/")) | |
215 { | |
216 /* *authorized = TRUE; */ | |
217 if (HTTP_DEBUG) | |
218 GT->DBGFN (GT, "received unimplemented Browse Host request"); | |
219 | |
220 return NULL; | |
221 } | |
222 | |
223 if (authorized) | |
224 *authorized = FALSE; | |
225 | |
226 if (!(path0 = path = STRDUP (s_path))) | |
227 return NULL; | |
228 | |
229 if (HTTP_DEBUG) | |
230 GT->dbg (GT, "path=%s", path); | |
231 | |
232 /* get rid of leading slash */ | |
233 string_sep (&path, "/"); | |
234 namespace = string_sep (&path, "/"); | |
235 | |
236 if (!namespace || !path) | |
237 { | |
238 GT->DBGFN (GT, "null namespace or path: %s %s\n", namespace, path); | |
239 free (path0); | |
240 return NULL; | |
241 } | |
242 | |
243 /* | |
244 * If the client supplied "X-Gnutella-Content-URN: ", lookup | |
245 * by that instead of the request. | |
246 */ | |
247 content_urns = dataset_lookupstr (xfer->header, "x-gnutella-content-urn"); | |
248 | |
249 if (content_urns) | |
250 file = lookup_urns (xfer, content_urns); | |
251 else if (!strcasecmp (namespace, "get")) | |
252 file = lookup_index (xfer, path); | |
253 else if (!strcasecmp (namespace, "uri-res")) | |
254 file = lookup_uri_res (xfer, path); | |
255 else | |
256 file = lookup_hpath (namespace, xfer, path); | |
257 | |
258 /* | |
259 * Set xfer->content_urn [which replies with 'X-Gnutella-Content-URN'] | |
260 * to a comma-separated list of all URNs for this file. | |
261 */ | |
262 xfer->content_urns = gt_share_local_get_urns (file); | |
263 | |
264 if (!file) | |
265 { | |
266 if (HTTP_DEBUG) | |
267 GT->DBGFN (GT, "bad request: /%s/%s", namespace, path); | |
268 | |
269 free (path0); | |
270 return NULL; | |
271 } | |
272 | |
273 free (path0); | |
274 | |
275 if (!share_complete (file)) | |
276 return NULL; | |
277 | |
278 /* argh, need to return static data */ | |
279 snprintf (open_path, sizeof (open_path) - 1, "%s", share_get_hpath (file)); | |
280 | |
281 /* try to fill in the hash */ | |
282 xfer->hash = share_dsp_hash (file, "SHA1"); | |
283 | |
284 return open_path; | |
285 } | |
286 | |
287 /******************************************************************************/ | |
288 | |
289 static char *index_request (char *request, size_t size, | |
290 uint32_t index, const char *filename) | |
291 { | |
292 /* | |
293 * The filename may not have ever been set. Fail the request in | |
294 * this case. | |
295 */ | |
296 if (!filename || string_isempty (filename)) | |
297 return NULL; | |
298 | |
299 /* | |
300 * Filename is encoded, we don't support sending unecoded requests | |
301 * anymore. NOTE: filename doesnt have leading '/' here, that may change | |
302 */ | |
303 snprintf (request, size - 1, "/get/%u/%s", index, filename); | |
304 return request; | |
305 } | |
306 | |
307 /* | |
308 * Setup a request string. Try request-by-hash (/uri-res/N2R?urn:sha1:..), | |
309 * but if there are any problems, fallback to a "/get/<index>/<filename>" | |
310 * request. | |
311 */ | |
312 static char *request_str (Source *source, uint32_t index, char *filename) | |
313 { | |
314 static char request[RW_BUFFER]; | |
315 char *hash = source->hash; | |
316 GtSource *gt; | |
317 | |
318 gt = source->udata; | |
319 assert (gt != NULL); | |
320 | |
321 /* | |
322 * Do a uri-res request unless one has failed already or | |
323 * if we have no filename and thus no choice but to use the hash. | |
324 * (doesn't happen currently but will for download-mesh sources | |
325 * that only have the hash and no filename) | |
326 */ | |
327 if (hash && (!gt->uri_res_failed || string_isempty (filename))) | |
328 { | |
329 char *str0, *str; | |
330 | |
331 if (!(str0 = STRDUP (hash))) | |
332 return index_request (request, sizeof (request), index, filename); | |
333 | |
334 str = str0; | |
335 string_sep (&str, ":"); | |
336 | |
337 /* hashes are canonically uppercase on the gnet */ | |
338 string_upper (str); | |
339 | |
340 if (str) | |
341 { | |
342 snprintf (request, sizeof (request) - 1, | |
343 "/uri-res/N2R?urn:sha1:%s", str); | |
344 free (str0); | |
345 return request; | |
346 } | |
347 | |
348 free (str0); | |
349 } | |
350 | |
351 return index_request (request, sizeof (request), index, filename); | |
352 } | |
353 | |
354 /*****************************************************************************/ | |
355 /* PUSH HANDLING */ | |
356 | |
357 /* | |
358 * This code has to deal with some tricky race conditions involving | |
359 * chunk timeouts and pushes. | |
360 */ | |
361 | |
362 static GtPushSource *gt_push_source_new (gt_guid_t *guid, in_addr_t ip, | |
363 in_addr_t src_ip) | |
364 { | |
365 GtPushSource *src; | |
366 | |
367 if (!(src = MALLOC (sizeof (GtPushSource)))) | |
368 return NULL; | |
369 | |
370 src->guid = gt_guid_dup (guid); | |
371 src->ip = ip; | |
372 src->src_ip = src_ip; | |
373 src->xfers = NULL; | |
374 src->connections = NULL; | |
375 | |
376 push_source_reset_last_sent (src); | |
377 | |
378 return src; | |
379 } | |
380 | |
381 static void gt_push_source_free (GtPushSource *src) | |
382 { | |
383 if (!src) | |
384 return; | |
385 | |
386 assert (src->xfers == NULL); | |
387 assert (src->connections == NULL); | |
388 | |
389 free (src->guid); | |
390 free (src); | |
391 } | |
392 | |
393 /*****************************************************************************/ | |
394 | |
395 /* TODO: break this into two parts, first part looks in the | |
396 * list for matching ip. If none is found, look for | |
397 * a firewalled (local ip) push source. */ | |
398 static int find_ip (GtPushSource *src, in_addr_t *ip) | |
399 { | |
400 /* If the source is a local IP address behind a non-local one, | |
401 * authorize by just the client guid. Otherwise, use the IP. */ | |
402 if (gt_is_local_ip (src->ip, src->src_ip) || src->ip == *ip) | |
403 return 0; | |
404 | |
405 return -1; | |
406 } | |
407 | |
408 static List *lookup_source_list (gt_guid_t *guid) | |
409 { | |
410 List *src_list; | |
411 | |
412 if (!(src_list = dataset_lookup (gt_push_requests, guid, 16))) | |
413 return NULL; | |
414 | |
415 return src_list; | |
416 } | |
417 | |
418 static GtPushSource *push_source_lookup (gt_guid_t *guid, in_addr_t ip) | |
419 { | |
420 List *requests; | |
421 List *list; | |
422 | |
423 if (!(requests = lookup_source_list (guid))) | |
424 return NULL; | |
425 | |
426 list = list_find_custom (requests, &ip, (ListForeachFunc)find_ip); | |
427 return list_nth_data (list, 0); | |
428 } | |
429 | |
430 /*****************************************************************************/ | |
431 | |
432 static void insert_source_list (gt_guid_t *guid, List *src_list) | |
433 { | |
434 if (!gt_push_requests) | |
435 gt_push_requests = dataset_new (DATASET_HASH); | |
436 | |
437 dataset_insert (>_push_requests, guid, 16, src_list, 0); | |
438 } | |
439 | |
440 static void add_push_source (List *pushes, gt_guid_t *guid, in_addr_t ip, | |
441 in_addr_t src_ip) | |
442 { | |
443 GtPushSource *src; | |
444 List *old_list; | |
445 | |
446 if (!(src = gt_push_source_new (guid, ip, src_ip))) | |
447 return; | |
448 | |
449 if ((old_list = list_find_custom (pushes, &ip, (ListForeachFunc)find_ip))) | |
450 { | |
451 /* push source is already there */ | |
452 gt_push_source_free (src); | |
453 return; | |
454 } | |
455 | |
456 pushes = list_prepend (pushes, src); | |
457 insert_source_list (guid, pushes); | |
458 } | |
459 | |
460 void gt_push_source_add (gt_guid_t *guid, in_addr_t ip, in_addr_t src_ip) | |
461 { | |
462 List *pushes; | |
463 | |
464 pushes = lookup_source_list (guid); | |
465 add_push_source (pushes, guid, ip, src_ip); | |
466 } | |
467 | |
468 /*****************************************************************************/ | |
469 /* Timing controls for push requests */ | |
470 | |
471 static void push_source_reset_last_sent (GtPushSource *push_src) | |
472 { | |
473 push_src->last_sent = gt_uptime (); /* wrong */ | |
474 push_src->defer_time = 0.0; | |
475 } | |
476 | |
477 static void push_source_set_last_sent (gt_guid_t *guid, in_addr_t ip) | |
478 { | |
479 GtPushSource *src; | |
480 | |
481 if (!(src = push_source_lookup (guid, ip))) | |
482 return; | |
483 | |
484 time (&src->last_sent); | |
485 } | |
486 | |
487 static BOOL push_source_should_send (gt_guid_t *guid, in_addr_t ip) | |
488 { | |
489 GtPushSource *src; | |
490 double deferred; | |
491 time_t now; | |
492 | |
493 time (&now); | |
494 | |
495 if (!(src = push_source_lookup (guid, ip))) | |
496 return FALSE; | |
497 | |
498 deferred = difftime (now, src->last_sent); | |
499 | |
500 /* randomize the defer time a bit in order to not send pushes for all | |
501 * downloads at once */ | |
502 if (deferred < src->defer_time + -10.0 + 20.0 * rand() / (RAND_MAX + 1.0)) | |
503 return FALSE; | |
504 | |
505 /* | |
506 * Double the defer time interval (the minimum time between sent | |
507 * pushes for this source). | |
508 */ | |
509 src->defer_time *= 2; | |
510 if (src->defer_time >= PUSH_MAX_DEFER_TIME) | |
511 src->defer_time = PUSH_MAX_DEFER_TIME; | |
512 | |
513 if (src->defer_time == 0) | |
514 src->defer_time = PUSH_MIN_DEFER_TIME; | |
515 | |
516 return TRUE; | |
517 } | |
518 | |
519 /*****************************************************************************/ | |
520 | |
521 static void flush_inputs (TCPC *c) | |
522 { | |
523 int ret; | |
524 | |
525 assert (c->fd >= 0); | |
526 | |
527 /* queued writes arent used by the HTTP system in this plugin, | |
528 * so this should always be true */ | |
529 ret = tcp_flush (c, TRUE); | |
530 assert (ret == 0); | |
531 | |
532 input_remove_all (c->fd); | |
533 } | |
534 | |
535 static void continue_download (GtPushSource *push_src, GtTransfer *xfer, | |
536 TCPC *c) | |
537 { | |
538 Chunk *chunk; | |
539 | |
540 chunk = gt_transfer_get_chunk (xfer); | |
541 | |
542 /* remove all previous inputs */ | |
543 flush_inputs (c); | |
544 | |
545 /* HACK: remove the detach timeout */ | |
546 timer_remove_zero (&xfer->detach_timer); | |
547 | |
548 /* connect the TCPC and GtTransfer */ | |
549 gt_transfer_set_tcpc (xfer, c); | |
550 | |
551 /* update the IP and port for placing in Host: */ | |
552 peer_addr (c->fd, &xfer->ip, &xfer->port); | |
553 | |
554 /* the connection and the chunk have met up */ | |
555 gt_transfer_status (xfer, SOURCE_WAITING, "Received GIV response"); | |
556 | |
557 if (HTTP_DEBUG) | |
558 GT->DBGSOCK (GT, c, "Continuing download for %s", xfer->request); | |
559 | |
560 input_add (c->fd, xfer, INPUT_WRITE, | |
561 (InputCallback)gt_http_client_start, TIMEOUT_DEF); | |
562 } | |
563 | |
564 static void reset_conn (int fd, input_id id, TCPC *c) | |
565 { | |
566 /* | |
567 * We should only get here if some data was sent, or if it timed out. In | |
568 * which case we should close this connection, because it shouldn't be | |
569 * sending anything. | |
570 */ | |
571 if (HTTP_DEBUG) | |
572 { | |
573 if (fd == -1) | |
574 GT->DBGSOCK (GT, c, "connection timed out"); | |
575 else | |
576 GT->DBGSOCK (GT, c, "connection closed or sent invalid data"); | |
577 } | |
578 | |
579 gt_push_source_remove_conn (c); | |
580 tcp_close (c); | |
581 } | |
582 | |
583 static void store_conn (GtPushSource *src, TCPC *c) | |
584 { | |
585 flush_inputs (c); | |
586 | |
587 input_add (c->fd, c, INPUT_READ, | |
588 (InputCallback)reset_conn, PUSH_LIMBO_TIMEOUT); | |
589 | |
590 assert (!list_find (src->connections, c)); | |
591 src->connections = list_prepend (src->connections, c); | |
592 | |
593 if (HTTP_DEBUG) | |
594 GT->DBGSOCK (GT, c, "storing connection"); | |
595 } | |
596 | |
597 BOOL gt_push_source_add_conn (gt_guid_t *guid, in_addr_t ip, TCPC *c) | |
598 { | |
599 GtTransfer *xfer; | |
600 GtPushSource *push_src; | |
601 | |
602 if (!(push_src = push_source_lookup (guid, ip))) | |
603 { | |
604 if (HTTP_DEBUG) | |
605 { | |
606 GT->err (GT, "couldn't find push source %s:[%s]", | |
607 gt_guid_str (guid), net_ip_str (ip)); | |
608 } | |
609 | |
610 tcp_close (c); | |
611 return FALSE; | |
612 } | |
613 | |
614 /* | |
615 * Don't allow too many connections in flight from the same remote user. | |
616 */ | |
617 if (list_length (push_src->connections) >= PUSH_MAX_LIMBO) | |
618 { | |
619 if (HTTP_DEBUG) | |
620 { | |
621 GT->DBGSOCK (GT, c, "too many push connections from %s, closing", | |
622 gt_guid_str (guid)); | |
623 } | |
624 | |
625 tcp_close (c); | |
626 return FALSE; | |
627 } | |
628 | |
629 /* | |
630 * Since we now know this push source is alive, reset the push send | |
631 * tracking time: in case the connection is lost, we'll resend the push | |
632 * right away instead of waiting. | |
633 */ | |
634 push_source_reset_last_sent (push_src); | |
635 | |
636 /* | |
637 * Store the connection if there are no GtTransfer requests from | |
638 * giFT at the moment. | |
639 */ | |
640 if (!push_src->xfers) | |
641 { | |
642 store_conn (push_src, c); | |
643 return FALSE; | |
644 } | |
645 | |
646 xfer = list_nth_data (push_src->xfers, 0); | |
647 push_src->xfers = list_remove (push_src->xfers, xfer); | |
648 | |
649 continue_download (push_src, xfer, c); | |
650 return TRUE; | |
651 } | |
652 | |
653 /* return TRUE if there's a connection residing on this push source */ | |
654 static BOOL push_source_lookup_conn (gt_guid_t *guid, in_addr_t ip) | |
655 { | |
656 GtPushSource *push_src; | |
657 | |
658 if (!(push_src = push_source_lookup (guid, ip))) | |
659 return FALSE; | |
660 | |
661 if (push_src->connections) | |
662 { | |
663 if (HTTP_DEBUG) | |
664 GT->DBGFN (GT, "found push connection for %s", net_ip_str (ip)); | |
665 | |
666 return TRUE; | |
667 } | |
668 | |
669 return FALSE; | |
670 } | |
671 | |
672 static void store_xfer (GtPushSource *src, GtTransfer *xfer) | |
673 { | |
674 assert (!list_find (src->xfers, xfer)); | |
675 src->xfers = list_prepend (src->xfers, xfer); | |
676 } | |
677 | |
678 BOOL gt_push_source_add_xfer (gt_guid_t *guid, in_addr_t ip, | |
679 in_addr_t src_ip, GtTransfer *xfer) | |
680 { | |
681 TCPC *c; | |
682 GtPushSource *push_src; | |
683 | |
684 assert (xfer != NULL); | |
685 | |
686 /* create the source if it doesn't exist already */ | |
687 gt_push_source_add (guid, ip, src_ip); | |
688 | |
689 if (!(push_src = push_source_lookup (guid, ip))) | |
690 { | |
691 if (HTTP_DEBUG) | |
692 { | |
693 GT->err (GT, "couldn't find push source (%s:[%s]) for chunk %s", | |
694 gt_guid_str (guid), net_ip_str (ip), xfer->request); | |
695 } | |
696 | |
697 return FALSE; | |
698 } | |
699 | |
700 /* | |
701 * Store the GtTransfer if there are no connections to service it | |
702 * at the moment. | |
703 */ | |
704 if (!push_src->connections) | |
705 { | |
706 store_xfer (push_src, xfer); | |
707 return FALSE; | |
708 } | |
709 | |
710 c = list_nth_data (push_src->connections, 0); | |
711 push_src->connections = list_remove (push_src->connections, c); | |
712 | |
713 continue_download (push_src, xfer, c); | |
714 return TRUE; | |
715 } | |
716 | |
717 /*****************************************************************************/ | |
718 | |
719 static BOOL remove_xfer (GtPushSource *src, GtTransfer *xfer) | |
720 { | |
721 src->xfers = list_remove (src->xfers, xfer); | |
722 return FALSE; | |
723 } | |
724 | |
725 static void remove_xfer_list (ds_data_t *key, ds_data_t *value, | |
726 GtTransfer *xfer) | |
727 { | |
728 List *src_list = value->data; | |
729 | |
730 list_foreach (src_list, (ListForeachFunc)remove_xfer, xfer); | |
731 } | |
732 | |
733 /* | |
734 * The chunk is being cancelled, so remove it from being tracked. | |
735 * | |
736 * After this, if the push recipient connects, we will have to wait | |
737 * for another chunk timeout before transmitting. | |
738 */ | |
739 void gt_push_source_remove_xfer (GtTransfer *xfer) | |
740 { | |
741 if (!xfer) | |
742 return; | |
743 | |
744 dataset_foreach (gt_push_requests, DS_FOREACH(remove_xfer_list), xfer); | |
745 } | |
746 | |
747 static BOOL remove_conn (GtPushSource *src, TCPC *c) | |
748 { | |
749 src->connections = list_remove (src->connections, c); | |
750 return FALSE; | |
751 } | |
752 | |
753 static void remove_conn_list (ds_data_t *key, ds_data_t *value, TCPC *c) | |
754 { | |
755 List *src_list = value->data; | |
756 | |
757 list_foreach (src_list, (ListForeachFunc)remove_conn, c); | |
758 } | |
759 | |
760 /* | |
761 * The connection from this push download closed | |
762 */ | |
763 void gt_push_source_remove_conn (TCPC *c) | |
764 { | |
765 if (!c) | |
766 return; | |
767 | |
768 dataset_foreach (gt_push_requests, DS_FOREACH(remove_conn_list), c); | |
769 } | |
770 | |
771 static BOOL cleanup_xfer (GtTransfer *xfer, void *udata) | |
772 { | |
773 gt_push_source_remove_xfer (xfer); | |
774 return TRUE; | |
775 } | |
776 | |
777 static BOOL cleanup_conn (TCPC *c, void *udata) | |
778 { | |
779 gt_push_source_remove_conn (c); | |
780 tcp_close (c); | |
781 return TRUE; | |
782 } | |
783 | |
784 static void remove_push_source (GtPushSource *src) | |
785 { | |
786 List *src_list; | |
787 | |
788 src_list = lookup_source_list (src->guid); | |
789 src_list = list_remove (src_list, src); | |
790 | |
791 insert_source_list (src->guid, src_list); | |
792 } | |
793 | |
794 void gt_push_source_remove (gt_guid_t *guid, in_addr_t ip, in_addr_t src_ip) | |
795 { | |
796 GtPushSource *src; | |
797 | |
798 if (!(src = push_source_lookup (guid, ip))) | |
799 return; | |
800 | |
801 /* cleanup all the chunks and connections */ | |
802 src->xfers = | |
803 list_foreach_remove (src->xfers, (ListForeachFunc)cleanup_xfer, | |
804 NULL); | |
805 src->connections = | |
806 list_foreach_remove (src->connections, (ListForeachFunc)cleanup_conn, | |
807 NULL); | |
808 | |
809 /* remove this source from the global list */ | |
810 remove_push_source (src); | |
811 | |
812 gt_push_source_free (src); | |
813 } | |
814 | |
815 /*****************************************************************************/ | |
816 | |
817 static BOOL detach_timeout (void *udata) | |
818 { | |
819 GtTransfer *xfer = udata; | |
820 | |
821 /* Added this on 2004-12-22 to track observed assertion failure in | |
822 * gt_transfer_get_chunk. -- mkern | |
823 */ | |
824 if (!xfer->chunk || xfer->chunk->udata != xfer) | |
825 { | |
826 GT->DBGFN (GT, "Detach timeout troubles. status = %d, " | |
827 "text = %s, xfer->ip = %s, " | |
828 "xfer = %p, xfer->chunk->udata = %p, " | |
829 "xfer->detach_timer = 0x%X", | |
830 xfer->detach_status, xfer->detach_msgtxt, | |
831 net_ip_str (xfer->ip), xfer, | |
832 xfer->chunk->udata, xfer->detach_timer); | |
833 } | |
834 | |
835 /* Sometimes gt_transfer_status will trigger an | |
836 * assert (xfer->chunk->udata == xfer) failure in gt_transfer_get_chunk. | |
837 * But why? Is xfer already freed? Does it have another chunk and the | |
838 * timer was not removed? | |
839 */ | |
840 gt_transfer_status (xfer, xfer->detach_status, xfer->detach_msgtxt); | |
841 gt_transfer_close (xfer, TRUE); | |
842 | |
843 return FALSE; | |
844 } | |
845 | |
846 /* | |
847 * Attach a timer that will "detach" the GtTransfer from the Chunk by | |
848 * cancelling it, but pretend that the Source is in some other state besides | |
849 * "cancelled" or "timed out" by changing the status text. | |
850 * | |
851 * This is useful to keep a semi-consistent UI in certain situations, such as | |
852 * sending out push requests, and cancelling requests when the remote side has | |
853 * queued our request. | |
854 */ | |
855 static void detach_transfer_in (GtTransfer *xfer, SourceStatus status, | |
856 char *status_txt, time_t interval) | |
857 { | |
858 char *msg; | |
859 | |
860 msg = STRDUP (status_txt); | |
861 | |
862 gt_transfer_status (xfer, status, msg); | |
863 xfer->detach_status = status; | |
864 | |
865 free (xfer->detach_msgtxt); | |
866 xfer->detach_msgtxt = msg; | |
867 | |
868 xfer->detach_timer = timer_add (interval, | |
869 (TimerCallback)detach_timeout, xfer); | |
870 } | |
871 | |
872 /* | |
873 * Detach an GtTransfer from its Chunk. | |
874 */ | |
875 static void detach_transfer (GtTransfer *xfer, SourceStatus status, | |
876 char *msgtxt) | |
877 { | |
878 /* | |
879 * Cancelling from p->download_start will cause download_pause() to crash. | |
880 * So, the detach must happen from timer context. | |
881 */ | |
882 detach_transfer_in (xfer, status, msgtxt, 2 * SECONDS); | |
883 } | |
884 | |
885 /*****************************************************************************/ | |
886 | |
887 static void send_push (GtTransfer *xfer, GtSource *gt, TCPC *server) | |
888 { | |
889 GtPacket *packet; | |
890 | |
891 if (!(packet = gt_packet_new (GT_MSG_PUSH, PUSH_MAX_TTL, NULL))) | |
892 return; | |
893 | |
894 gt_packet_put_ustr (packet, gt->guid, 16); | |
895 gt_packet_put_uint32 (packet, gt->index); | |
896 gt_packet_put_ip (packet, GT_NODE(server)->my_ip); | |
897 gt_packet_put_port (packet, GT_SELF->gt_port); | |
898 | |
899 if (gt_packet_error (packet)) | |
900 { | |
901 gt_packet_free (packet); | |
902 return; | |
903 } | |
904 | |
905 gt_packet_send (server, packet); | |
906 gt_packet_free (packet); | |
907 | |
908 /* | |
909 * Don't wait for the whole Chunk timeout -- that keeps the Chunk | |
910 * occupied for too long if there are other active sources (the Chunk | |
911 * also times out longer and longer each time, so this gets worse | |
912 * the longer the transfer is inactive). | |
913 * | |
914 * This is really an infelicity of the Chunk system. | |
915 */ | |
916 detach_transfer_in (xfer, SOURCE_QUEUED_REMOTE, "Sent PUSH request", | |
917 PUSH_WAIT_INTERVAL); | |
918 | |
919 /* set the last time we sent a push to now */ | |
920 push_source_set_last_sent (gt->guid, gt->user_ip); | |
921 } | |
922 | |
923 static BOOL send_push_to_server (in_addr_t server_ip, in_port_t server_port, | |
924 GtTransfer *xfer, GtSource *gt) | |
925 { | |
926 GtNode *server; | |
927 | |
928 if (!(server = gt_node_lookup (server_ip, server_port))) | |
929 { | |
930 server = gt_node_register (server_ip, server_port, | |
931 GT_NODE_ULTRA); | |
932 } | |
933 | |
934 if (!server) | |
935 { | |
936 GT->err (GT, "couldn't register server"); | |
937 return FALSE; | |
938 } | |
939 | |
940 if (server->state & (GT_NODE_CONNECTED | GT_NODE_CONNECTING_2)) | |
941 { | |
942 assert (GT_CONN(server) != NULL); | |
943 | |
944 /* Server is in a state for receiving packets -- send the push */ | |
945 send_push (xfer, gt, GT_CONN(server)); | |
946 return TRUE; | |
947 } | |
948 else if (server->state & GT_NODE_CONNECTING_1) | |
949 { | |
950 /* dont try to connect again; wait till we're connected */ | |
951 return FALSE; | |
952 } | |
953 else if (gt_conn_need_connections (GT_NODE_ULTRA) > 0 && | |
954 !server->tried_connect && | |
955 gt_connect (server) >= 0) | |
956 { | |
957 /* | |
958 * We've tried to connect to the server so we could deliver the push | |
959 * request eventually NOTE: this doesnt send a push until the next | |
960 * chunk timeout. | |
961 */ | |
962 return FALSE; | |
963 } | |
964 | |
965 return FALSE; | |
966 } | |
967 | |
968 static void handle_push_download (Chunk *chunk, GtTransfer *xfer, GtSource *gt) | |
969 { | |
970 GtNode *server; | |
971 | |
972 /* | |
973 * If this succeeds, we already have a connection to this | |
974 * user and the transfer will continue by using that connection. | |
975 * | |
976 * TODO: the gt_push_source_add() should be used by some | |
977 * per-source data structure | |
978 */ | |
979 if (gt_push_source_add_xfer (gt->guid, gt->user_ip, gt->server_ip, xfer)) | |
980 return; | |
981 | |
982 /* | |
983 * Dont send pushes too often. Maybe should use a global queue instead. | |
984 * | |
985 * NOTE: we can't free the xfer here because we have stored it. | |
986 */ | |
987 if (push_source_should_send (gt->guid, gt->user_ip) == FALSE) | |
988 { | |
989 /* don't occupy the Chunk forever */ | |
990 detach_transfer_in (xfer, SOURCE_QUEUED_REMOTE, "Awaiting connection", | |
991 PUSH_WAIT_INTERVAL); | |
992 return; | |
993 } | |
994 | |
995 /* | |
996 * Next, try to find the server that supplied this result, | |
997 * and send them a push. | |
998 */ | |
999 if (send_push_to_server (gt->server_ip, gt->server_port, xfer, gt)) | |
1000 return; | |
1001 | |
1002 /* | |
1003 * Finally, try sending to a random connected server. | |
1004 * | |
1005 * TODO: these should be rate-limited, either globally or | |
1006 * per-source. | |
1007 */ | |
1008 if ((server = gt_conn_random (GT_NODE_ULTRA, GT_NODE_CONNECTED))) | |
1009 { | |
1010 send_push_to_server (server->ip, server->gt_port, xfer, gt); | |
1011 return; | |
1012 } | |
1013 | |
1014 detach_transfer (xfer, SOURCE_QUEUED_REMOTE, "No PUSH route"); | |
1015 } | |
1016 | |
1017 static BOOL set_request (GtTransfer *xfer, Chunk *chunk, Source *source, | |
1018 GtSource *gt_src) | |
1019 { | |
1020 char *request; | |
1021 | |
1022 if (!chunk || !xfer) | |
1023 return FALSE; | |
1024 | |
1025 request = request_str (source, gt_src->index, gt_src->filename); | |
1026 | |
1027 if (!gt_transfer_set_request (xfer, request)) | |
1028 { | |
1029 GT->DBGFN (GT, "UI made an invalid request for '%s'", request); | |
1030 return FALSE; | |
1031 } | |
1032 | |
1033 /* connect the xfer and the chunk */ | |
1034 gt_transfer_set_chunk (xfer, chunk); | |
1035 | |
1036 return TRUE; | |
1037 } | |
1038 | |
1039 static BOOL should_push (GtSource *gt) | |
1040 { | |
1041 TCPC *persistent; | |
1042 | |
1043 /* we cannot push if there is no guid to send the push to */ | |
1044 if (gt_guid_is_empty (gt->guid)) | |
1045 return FALSE; | |
1046 | |
1047 persistent = gt_http_connection_lookup (GT_TRANSFER_DOWNLOAD, | |
1048 gt->user_ip, | |
1049 gt->user_port); | |
1050 | |
1051 /* need to close the connection to re-add it to the list, because | |
1052 * _lookup removes it from the persistent connection list */ | |
1053 gt_http_connection_close (GT_TRANSFER_DOWNLOAD, persistent, FALSE); | |
1054 | |
1055 /* if we already have a connection don't send a push */ | |
1056 if (persistent) | |
1057 return FALSE; | |
1058 | |
1059 /* now check for a persistent "pushed" connection, which would be stored | |
1060 * separately from a directly connected one */ | |
1061 if (push_source_lookup_conn (gt->guid, gt->user_ip)) | |
1062 return TRUE; | |
1063 | |
1064 /* send a push if the IP is local */ | |
1065 if (gt_is_local_ip (gt->user_ip, gt->server_ip)) | |
1066 return TRUE; | |
1067 | |
1068 /* don't send a push if we cannot receive incoming connections */ | |
1069 if (gt_bind_is_firewalled()) | |
1070 return FALSE; | |
1071 | |
1072 /* send a push if they set the firewalled bit */ | |
1073 if (gt->firewalled) | |
1074 return TRUE; | |
1075 | |
1076 /* the last connection attempt failed, so try a push */ | |
1077 if (gt->connect_failed) | |
1078 return TRUE; | |
1079 | |
1080 return FALSE; | |
1081 } | |
1082 | |
1083 static void handle_download (Chunk *chunk, GtTransfer *xfer, GtSource *gt) | |
1084 { | |
1085 /* | |
1086 * Send a push, or connect directly. | |
1087 */ | |
1088 if (should_push (gt)) | |
1089 { | |
1090 /* (possibly) retry a connection attempt next time */ | |
1091 gt->connect_failed = FALSE; | |
1092 | |
1093 handle_push_download (chunk, xfer, gt); | |
1094 } | |
1095 else | |
1096 { | |
1097 gt_http_client_get (chunk, xfer); | |
1098 } | |
1099 } | |
1100 | |
1101 static BOOL download_is_queued (GtSource *gt) | |
1102 { | |
1103 /* back out if the request is still too early */ | |
1104 if (time (NULL) < gt->retry_time) | |
1105 return TRUE; | |
1106 | |
1107 return FALSE; | |
1108 } | |
1109 | |
1110 int gnutella_download_start (Protocol *p, Transfer *transfer, Chunk *chunk, | |
1111 Source *source) | |
1112 { | |
1113 GtTransfer *xfer; | |
1114 GtSource *gt; | |
1115 off_t start; | |
1116 off_t stop; | |
1117 | |
1118 gt = source->udata; | |
1119 assert (gt != NULL); | |
1120 | |
1121 /* giftd should send us only deactivated Chunks */ | |
1122 assert (chunk->udata == NULL); | |
1123 | |
1124 /* free the Source URL and update it with any format changes */ | |
1125 replace_url (source, gt); | |
1126 | |
1127 /* thank you, pretender :) */ | |
1128 start = chunk->start + chunk->transmit; | |
1129 stop = chunk->stop; | |
1130 | |
1131 if (!(xfer = gt_transfer_new (GT_TRANSFER_DOWNLOAD, source, | |
1132 gt->user_ip, gt->user_port, start, stop))) | |
1133 { | |
1134 GT->DBGFN (GT, "gt_transfer_new failed"); | |
1135 return FALSE; | |
1136 } | |
1137 | |
1138 if (!set_request (xfer, chunk, source, gt)) | |
1139 { | |
1140 gt_transfer_close (xfer, TRUE); | |
1141 return FALSE; | |
1142 } | |
1143 | |
1144 if (download_is_queued (gt)) | |
1145 { | |
1146 detach_transfer (xfer, SOURCE_QUEUED_REMOTE, gt->status_txt); | |
1147 return TRUE; | |
1148 } | |
1149 | |
1150 handle_download (chunk, xfer, gt); | |
1151 | |
1152 return TRUE; | |
1153 } | |
1154 | |
1155 void gnutella_download_stop (Protocol *p, Transfer *transfer, Chunk *chunk, | |
1156 Source *source, BOOL complete) | |
1157 { | |
1158 gt_download_cancel (chunk, NULL); | |
1159 } | |
1160 | |
1161 int gnutella_upload_start (Protocol *p, Transfer *transfer, Chunk *chunk, | |
1162 Source *source, unsigned long avail) | |
1163 { | |
1164 return TRUE; | |
1165 } | |
1166 | |
1167 void gnutella_upload_stop (Protocol *p, Transfer *transfer, Chunk *chunk, | |
1168 Source *source) | |
1169 { | |
1170 gt_upload_cancel (chunk, NULL); | |
1171 } | |
1172 | |
1173 int gnutella_chunk_suspend (Protocol *p, Transfer *transfer, Chunk *chunk, | |
1174 Source *source) | |
1175 { | |
1176 return gt_chunk_suspend (chunk, transfer, NULL); | |
1177 } | |
1178 | |
1179 int gnutella_chunk_resume (Protocol *p, Transfer *transfer, Chunk *chunk, | |
1180 Source *source) | |
1181 { | |
1182 return gt_chunk_resume (chunk, transfer, NULL); | |
1183 } |