source: branches/scrape/libtransmission/tracker.c @ 1136

Last change on this file since 1136 was 1136, checked in by livings124, 16 years ago

don't force more announces if the torrent is complete, and determining if there are many peers can now acknowledge a change for having many to not having many

  • Property svn:keywords set to Date Rev Author Id
File size: 18.1 KB
Line 
1/******************************************************************************
2 * $Id: tracker.c 1136 2006-11-26 03:12:43Z livings124 $
3 *
4 * Copyright (c) 2005-2006 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include "transmission.h"
26
27struct tr_tracker_s
28{
29    tr_torrent_t * tor;
30
31    char         * id;
32
33    char           started;
34    char           completed;
35    char           stopped;
36
37    int            interval;
38    int            scrapeInterval;
39    int            seeders;
40    int            leechers;
41    int            hasManyPeers;
42    int            downloaded;
43
44    uint64_t       dateTry;
45    uint64_t       dateOk;
46    uint64_t       dateScrape;
47    int            lastScrapeFailed;
48
49#define TC_ATTEMPT_NOREACH 1
50#define TC_ATTEMPT_ERROR   2
51#define TC_ATTEMPT_OK      4
52    char           lastAttempt;
53    int            scrapeNeeded;
54
55    tr_http_t    * http;
56    tr_http_t    * httpScrape;
57
58    int            bindPort;
59    int            newPort;
60};
61
62static tr_http_t * getQuery   ( tr_tracker_t * tc );
63static void        readAnswer ( tr_tracker_t * tc, const char *, int );
64static void readScrapeAnswer( tr_tracker_t * tc, const char * data, int len );
65
66tr_tracker_t * tr_trackerInit( tr_torrent_t * tor )
67{
68    tr_tracker_t * tc;
69
70    tc           = calloc( 1, sizeof( tr_tracker_t ) );
71    tc->tor      = tor;
72    tc->id       = tor->id;
73
74    tc->started  = 1;
75
76    tc->interval = 300;
77    tc->scrapeInterval = 600;
78    tc->seeders  = -1;
79    tc->leechers = -1;
80    tc->downloaded  = -1;
81
82    tc->lastAttempt = TC_ATTEMPT_NOREACH;
83    tc->scrapeNeeded = 0;
84   
85    tc->lastScrapeFailed = 0;
86
87    tc->bindPort = *(tor->bindPort);
88    tc->newPort  = -1;
89
90    return tc;
91}
92
93static int shouldConnect( tr_tracker_t * tc )
94{
95    uint64_t now = tr_date();
96   
97    /* Unreachable tracker, try 10 seconds before trying again */
98    if( tc->lastAttempt == TC_ATTEMPT_NOREACH &&
99        now < tc->dateTry + 10000 )
100    {
101        return 0;
102    }
103
104    /* The tracker rejected us (like 4XX code, unauthorized IP...),
105       don't hammer it - we'll probably get the same answer next time
106       anyway */
107    if( tc->lastAttempt == TC_ATTEMPT_ERROR &&
108        now < tc->dateTry + 1000 * tc->interval )
109    {
110        return 0;
111    }
112
113    /* Do we need to send an event? */
114    if( tc->started || tc->completed || tc->stopped || 0 < tc->newPort )
115    {
116        return 1;
117    }
118
119    /* Should we try and get more peers? */
120    if( now > tc->dateOk + 1000 * tc->interval )
121    {
122        return 1;
123    }
124
125    /* If there is quite a lot of people on this torrent, stress
126       the tracker a bit until we get a decent number of peers */
127    if( tc->hasManyPeers && !tc->tor->finished )
128    {
129        if( tc->tor->peerCount < 5 && now > tc->dateOk + 10000 )
130        {
131            return 1;
132        }
133        if( tc->tor->peerCount < 10 && now > tc->dateOk + 20000 )
134        {
135            return 1;
136        }
137        if( tc->tor->peerCount < 15 && now > tc->dateOk + 30000 )
138        {
139            return 1;
140        }
141    }
142
143    return 0;
144}
145
146static int shouldScrape( tr_tracker_t * tc )
147{
148    // scrape not supported
149    if( !tc->tor->scrape[0] )
150    {
151        return 0;
152    }
153
154    uint64_t now = tr_date();
155    uint64_t interval = 1000 * MAX(tc->scrapeInterval, 60);
156   
157    // scrape half as often if there is no need to
158    if (!tc->scrapeNeeded && !tc->lastScrapeFailed)
159    {
160        interval *= 2;
161    }
162   
163    return now > tc->dateScrape + interval;
164}
165
166void tr_trackerChangePort( tr_tracker_t * tc, int port )
167{
168    tc->newPort = port;
169}
170
171int tr_trackerPulse( tr_tracker_t * tc )
172{
173    tr_torrent_t * tor = tc->tor;
174    tr_info_t    * inf = &tor->info;
175    const char   * data;
176    int            len;
177
178    if( ( NULL == tc->http ) && shouldConnect( tc ) )
179    {
180        if( tr_fdSocketWillCreate( tor->fdlimit, 1 ) )
181        {
182            return 0;
183        }
184        tc->dateTry = tr_date();
185        tc->http = getQuery( tc );
186
187        tr_inf( "Tracker: connecting to %s:%d (%s)",
188                inf->trackerAddress, inf->trackerPort,
189                tc->started ? "sending 'started'" :
190                ( tc->completed ? "sending 'completed'" :
191                  ( tc->stopped ? "sending 'stopped'" :
192                    ( 0 < tc->newPort ? "sending 'stopped' to change port" :
193                      "getting peers" ) ) ) );
194    }
195   
196    if( NULL != tc->http )
197    {
198        switch( tr_httpPulse( tc->http, &data, &len ) )
199        {
200            case TR_WAIT:
201                break;
202
203            case TR_ERROR:
204                tr_httpClose( tc->http );
205                tr_fdSocketClosed( tor->fdlimit, 1 );
206                tc->http    = NULL;
207                tc->dateTry = tr_date();
208                break;
209
210            case TR_OK:
211                readAnswer( tc, data, len );
212                tr_httpClose( tc->http );
213                tc->http = NULL;
214                tr_fdSocketClosed( tor->fdlimit, 1 );
215                break;
216        }
217    }
218   
219    if( ( NULL == tc->httpScrape ) && shouldScrape( tc ) )
220    {
221        if( tr_fdSocketWillCreate( tor->fdlimit, 1 ) )
222        {
223            return 0;
224        }
225        tc->dateScrape = tr_date();
226        tc->httpScrape = tr_httpClient( TR_HTTP_GET, inf->trackerAddress, inf->trackerPort,
227                            "%s%sinfo_hash=%s", tor->scrape, strchr( tor->scrape, '?' ) ?
228                            "&" : "?", tor->escapedHashString );
229        tr_inf( "Scrape: sent http request to %s:%d",
230                    inf->trackerAddress, inf->trackerPort );
231    }
232   
233    if( NULL != tc->httpScrape )
234    {
235        switch( tr_httpPulse( tc->httpScrape, &data, &len ) )
236        {
237            case TR_WAIT:
238                return 0;
239
240            case TR_ERROR:
241                tr_httpClose( tc->httpScrape );
242                tr_fdSocketClosed( tor->fdlimit, 1 );
243                tc->httpScrape    = NULL;
244                tc->lastScrapeFailed = 1;
245                return 0;
246
247            case TR_OK:
248                readScrapeAnswer( tc, data, len );
249                tr_httpClose( tc->httpScrape );
250                tc->httpScrape = NULL;
251                tr_fdSocketClosed( tor->fdlimit, 1 );
252                break;
253        }
254    }
255
256    return 0;
257}
258
259void tr_trackerCompleted( tr_tracker_t * tc )
260{
261    tc->started   = 0;
262    tc->completed = 1;
263    tc->stopped   = 0;
264}
265
266void tr_trackerStopped( tr_tracker_t * tc )
267{
268    tr_torrent_t * tor = tc->tor;
269
270    if( NULL != tc->http )
271    {
272        /* If we are already sendy a query at the moment, we need to
273           reconnect */
274        tr_httpClose( tc->http );
275        tc->http = NULL;
276        tr_fdSocketClosed( tor->fdlimit, 1 );
277    }
278    if( NULL != tc->httpScrape )
279    {
280        /* If we are already sendy a query at the moment, we need to
281           reconnect */
282        tr_httpClose( tc->httpScrape );
283        tc->httpScrape = NULL;
284        tr_fdSocketClosed( tor->fdlimit, 1 );
285    }
286
287    tc->started   = 0;
288    tc->completed = 0;
289    tc->stopped   = 1;
290
291    /* Even if we have connected recently, reconnect right now */
292    tc->dateTry = 0;
293}
294
295void tr_trackerClose( tr_tracker_t * tc )
296{
297    tr_torrent_t * tor = tc->tor;
298
299    if( NULL != tc->http )
300    {
301        tr_httpClose( tc->http );
302        tr_fdSocketClosed( tor->fdlimit, 1 );
303    }
304    if( NULL != tc->httpScrape )
305    {
306        tr_httpClose( tc->httpScrape );
307        tr_fdSocketClosed( tor->fdlimit, 1 );
308    }
309   
310    if( tor->trackerid )
311        free( tor->trackerid );
312   
313    free( tc );
314}
315
316static tr_http_t * getQuery( tr_tracker_t * tc )
317{
318    tr_torrent_t * tor = tc->tor;
319    tr_info_t    * inf = &tor->info;
320
321    char         * event;
322    char         * trackerid;
323    uint64_t       left;
324    uint64_t       down;
325    uint64_t       up;
326    char         * start;
327    int            numwant = 50;
328
329    down = tor->downloadedCur;
330    up = tor->uploadedCur;
331    if( tc->started )
332    {
333        event = "&event=started";
334        down = up = 0;
335       
336        if( 0 < tc->newPort )
337        {
338            tc->bindPort = tc->newPort;
339            tc->newPort = -1;
340        }
341    }
342    else if( tc->completed )
343    {
344        event = "&event=completed";
345    }
346    else if( tc->stopped || 0 < tc->newPort )
347    {
348        event = "&event=stopped";
349        numwant = 0;
350    }
351    else
352    {
353        event = "";
354    }
355
356    if( NULL == strchr( inf->trackerAnnounce, '?' ) )
357    {
358        start = "?";
359    }
360    else
361    {
362        start = "&";
363    }
364
365    left = tr_cpLeftBytes( tor->completion );
366   
367    trackerid = tor->trackerid ? ( "&trackerid=%s", tor->trackerid ) : ""; 
368
369    return tr_httpClient( TR_HTTP_GET, inf->trackerAddress,
370                          inf->trackerPort,
371                          "%s%s"
372                          "info_hash=%s&"
373                          "peer_id=%s&"
374                          "port=%d&"
375                          "uploaded=%"PRIu64"&"
376                          "downloaded=%"PRIu64"&"
377                          "left=%"PRIu64"&"
378                          "compact=1&"
379                          "numwant=%d&"
380                          "key=%s"
381                          "%s"
382                          "%s",
383                          inf->trackerAnnounce, start, tor->escapedHashString, tc->id,
384                          tc->bindPort, up, down, left, numwant, tor->key, trackerid, event );
385}
386
387static void readAnswer( tr_tracker_t * tc, const char * data, int len )
388{
389    tr_torrent_t * tor = tc->tor;
390    int i;
391    int code;
392    benc_val_t   beAll;
393    benc_val_t * bePeers, * beFoo;
394    const uint8_t * body;
395    int bodylen;
396    int shouldfree;
397
398    tc->dateTry = tr_date();
399    code = tr_httpResponseCode( data, len );
400    if( 0 > code )
401    {
402        /* We don't have a valid HTTP status line */
403        tr_inf( "Tracker: invalid HTTP status line" );
404        tc->lastAttempt = TC_ATTEMPT_NOREACH;
405        return;
406    }
407
408    if( !TR_HTTP_STATUS_OK( code ) )
409    {
410        /* we didn't get a 2xx status code */
411        tr_err( "Tracker: invalid HTTP status code: %i", code );
412        tc->lastAttempt = TC_ATTEMPT_ERROR;
413        return;
414    }
415
416    /* find the end of the http headers */
417    body = (uint8_t *) tr_httpParse( data, len, NULL );
418    if( NULL == body )
419    {
420        tr_err( "Tracker: could not find end of HTTP headers" );
421        tc->lastAttempt = TC_ATTEMPT_NOREACH;
422        return;
423    }
424    bodylen = len - (body - (const uint8_t*)data);
425
426    /* Find and load the dictionary */
427    shouldfree = 0;
428    for( i = 0; i < bodylen; i++ )
429    {
430        if( !tr_bencLoad( &body[i], bodylen - i, &beAll, NULL ) )
431        {
432            shouldfree = 1;
433            break;
434        }
435    }
436
437    if( i >= bodylen )
438    {
439        if( tc->stopped || 0 < tc->newPort )
440        {
441            tc->lastAttempt = TC_ATTEMPT_OK;
442            goto nodict;
443        }
444        tr_err( "Tracker: no valid dictionary found in answer" );
445        tc->lastAttempt = TC_ATTEMPT_ERROR;
446        return;
447    }
448
449    // tr_bencPrint( &beAll );
450
451    if( ( bePeers = tr_bencDictFind( &beAll, "failure reason" ) ) )
452    {
453        tr_err( "Tracker: %s", bePeers->val.s.s );
454        tor->error |= TR_ETRACKER;
455        snprintf( tor->trackerError, sizeof( tor->trackerError ),
456                  "%s", bePeers->val.s.s );
457        tc->lastAttempt = TC_ATTEMPT_ERROR;
458        goto cleanup;
459    }
460
461    tor->error &= ~TR_ETRACKER;
462    tc->lastAttempt = TC_ATTEMPT_OK;
463
464    if( !tc->interval )
465    {
466        /* Get the tracker interval, ignore it if it is not between
467           10 sec and 5 mins */
468        if( !( beFoo = tr_bencDictFind( &beAll, "interval" ) ) ||
469            !( beFoo->type & TYPE_INT ) )
470        {
471            tr_err( "Tracker: no 'interval' field" );
472            goto cleanup;
473        }
474
475        tc->interval = beFoo->val.i;
476        tc->interval = MIN( tc->interval, 300 );
477        tc->interval = MAX( 10, tc->interval );
478
479        tr_inf( "Tracker: interval = %d seconds", tc->interval );
480    }
481
482    int scrapeNeeded = 0;
483    if( ( beFoo = tr_bencDictFind( &beAll, "complete" ) ) &&
484        ( beFoo->type & TYPE_INT ) )
485    {
486        tc->seeders = beFoo->val.i;
487    }
488    else
489    {
490        scrapeNeeded = 1;
491    }
492    if( ( beFoo = tr_bencDictFind( &beAll, "incomplete" ) ) &&
493        ( beFoo->type & TYPE_INT ) )
494    {
495        tc->leechers = beFoo->val.i;
496    }
497    else
498    {
499        scrapeNeeded = 1;
500    }
501    tc->scrapeNeeded = scrapeNeeded;
502   
503    if( !scrapeNeeded )
504        tc->hasManyPeers = tc->seeders + tc->leechers >= 50;
505   
506    if( beFoo = tr_bencDictFind( &beAll, "tracker id" ) )
507    {
508        if( tor->trackerid )
509            free( tor->trackerid );
510        tor->trackerid = strdup( beFoo->val.s.s );
511        tr_inf( "Tracker: tracker id = %s", tor->trackerid);
512    }
513   
514    if( !( bePeers = tr_bencDictFind( &beAll, "peers" ) ) )
515    {
516        if( tc->stopped || 0 < tc->newPort )
517        {
518            goto nodict;
519        }
520        tr_err( "Tracker: no \"peers\" field" );
521        goto cleanup;
522    }
523
524    if( bePeers->type & TYPE_LIST )
525    {
526        char * ip;
527        int    port;
528
529        /* Original protocol */
530        tr_inf( "Tracker: got %d peers", bePeers->val.l.count );
531
532        for( i = 0; i < bePeers->val.l.count; i++ )
533        {
534            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "ip" );
535            if( !beFoo )
536                continue;
537            ip = beFoo->val.s.s;
538            beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "port" );
539            if( !beFoo )
540                continue;
541            port = beFoo->val.i;
542
543            tr_peerAddOld( tor, ip, port );
544        }
545
546        if( bePeers->val.l.count >= 50 )
547        {
548            tc->hasManyPeers = 1;
549        }
550    }
551    else if( bePeers->type & TYPE_STR )
552    {
553        struct in_addr addr;
554        in_port_t      port;
555
556        /* "Compact" extension */
557        if( bePeers->val.s.i % 6 )
558        {
559            tr_err( "Tracker: \"peers\" of size %d",
560                    bePeers->val.s.i );
561            tr_lockUnlock( &tor->lock );
562            goto cleanup;
563        }
564
565        tr_inf( "Tracker: got %d peers", bePeers->val.s.i / 6 );
566        for( i = 0; i < bePeers->val.s.i / 6; i++ )
567        {
568            memcpy( &addr, &bePeers->val.s.s[6*i],   4 );
569            memcpy( &port, &bePeers->val.s.s[6*i+4], 2 );
570
571            tr_peerAddCompact( tor, addr, port );
572        }
573
574        if( bePeers->val.s.i / 6 >= 50 )
575        {
576            tc->hasManyPeers = 1;
577        }
578    }
579
580nodict:
581    /* Success */
582    tc->started   = 0;
583    tc->completed = 0;
584    tc->dateOk    = tr_date();
585
586    if( tc->stopped )
587    {
588        tor->status = TR_STATUS_STOPPED;
589        tc->stopped = 0;
590    }
591    else if( 0 < tc->newPort )
592    {
593        tc->started  = 1;
594    }
595
596cleanup:
597    if( shouldfree )
598    {
599        tr_bencFree( &beAll );
600    }
601}
602
603static void readScrapeAnswer( tr_tracker_t * tc, const char * data, int len )
604{
605    tr_torrent_t * tor = tc->tor;
606    int i;
607    int code;
608    const uint8_t * body;
609    int bodylen, ii;
610    benc_val_t scrape, * val1, * val2;
611
612    code = tr_httpResponseCode( data, len );
613    if( 0 > code )
614    {
615        /* We don't have a valid HTTP status line */
616        tr_inf( "Scrape: invalid HTTP status line" );
617        tc->lastScrapeFailed = 1;
618        return;
619    }
620
621    if( !TR_HTTP_STATUS_OK( code ) )
622    {
623        /* we didn't get a 2xx status code */
624        tr_err( "Scrape: invalid HTTP status code: %i", code );
625        tc->lastScrapeFailed = 1;
626        return;
627    }
628
629    /* find the end of the http headers */
630    body = (uint8_t *) tr_httpParse( data, len, NULL );
631    if( NULL == body )
632    {
633        tr_err( "Scrape: could not find end of HTTP headers" );
634        tc->lastScrapeFailed = 1;
635        return;
636    }
637   
638    tc->lastScrapeFailed = 0;
639   
640    bodylen = len - (body - (const uint8_t*)data);
641
642    for( ii = 0; ii < bodylen - 8; ii++ )
643    {
644        if( !memcmp( body + ii, "d5:files", 8 ) )
645        {
646            break;
647        }
648    }
649    if( ii >= bodylen - 8 )
650    {
651        return;
652    }
653    if( tr_bencLoad( body + ii, bodylen - ii, &scrape, NULL ) )
654    {
655        return;
656    }
657
658    val1 = tr_bencDictFind( &scrape, "files" );
659    if( !val1 )
660    {
661        tr_bencFree( &scrape );
662        return;
663    }
664    val1 = &val1->val.l.vals[1];
665    if( !val1 )
666    {
667        tr_bencFree( &scrape );
668        return;
669    }
670   
671    val2 = tr_bencDictFind( val1, "complete" );
672    if( !val2 )
673    {
674        tr_bencFree( &scrape );
675        return;
676    }
677    tc->seeders = val2->val.i;
678   
679    val2 = tr_bencDictFind( val1, "incomplete" );
680    if( !val2 )
681    {
682        tr_bencFree( &scrape );
683        return;
684    }
685    tc->leechers = val2->val.i;
686   
687    val2 = tr_bencDictFind( val1, "downloaded" );
688    if( !val2 )
689    {
690        tr_bencFree( &scrape );
691        return;
692    }
693    tc->downloaded = val2->val.i;
694   
695    val2 = tr_bencDictFind( val1, "flags" );
696    if (val2)
697    {
698        val2 = tr_bencDictFind( val2, "min_request_interval" );
699        if (val2)
700        {
701            tc->scrapeInterval = val2->val.i;
702        }
703    }
704   
705    tc->hasManyPeers = tc->seeders + tc->leechers >= 50;
706   
707    tr_bencFree( &scrape );
708}
709
710int tr_trackerSeeders( tr_tracker_t * tc )
711{
712    if( !tc )
713    {
714        return -1;
715    }
716    return tc->seeders;
717}
718
719int tr_trackerLeechers( tr_tracker_t * tc )
720{
721    if( !tc )
722    {
723        return -1;
724    }
725    return tc->leechers;
726}
727
728int tr_trackerDownloaded( tr_tracker_t * tc )
729{
730    if( !tc )
731    {
732        return -1;
733    }
734    return tc->downloaded;
735}
Note: See TracBrowser for help on using the repository browser.